主頁 > 後端開發 > 分享自研實作的多資料源(支持同DB不同表、跨DB表、記憶體資料、外部系統資料等)分頁查詢工具類實作原理及使用

分享自研實作的多資料源(支持同DB不同表、跨DB表、記憶體資料、外部系統資料等)分頁查詢工具類實作原理及使用

2022-03-14 06:23:02 後端開發

思考:

提起分頁查詢,想必任何一個開發人員(不論是新手還是老手)都能快速編碼實作,實作原理再簡單不過,無非就是寫一條SELECT查詢的SQL陳述句,ORDER BY分頁排序的欄位, 再結合limit (頁碼-1),每頁記錄數,這樣即可回傳指定頁碼的分頁記錄,類似SQL如下所示:

select * from table where 查詢條件 order by id limit 100,100; -- 這里假設是第2頁(limit 第1個值從0開始),每頁100條

那如果是想將多張表的記錄合并一起進行分頁查詢,我們又該如何實作呢?我估計稍微有點經驗的開發人員可能會立馬舉一反三,想到了通過UNION 多張表的方式來實作分頁查詢,類似SQL如下所示:

select * from
(select id,col1,col2,col3 from table1 where 查詢條件
union all
select id,cola as col1,colb as col2,colc as col3 from table2 where 查詢條件
) as t
order by t.id limit 100,100; -- 這里假設是第2頁(limit 第1個值從0開始),每頁100條

這樣實作有沒有問題呢?我覺得如果是UNION的多張小表(即:資料量相對較小)那么這樣實作成本最低最有效果,肯定是OK的,但如果UNION的多張表是一些大表(即:資料量相對較大,如:100W+)且有些表的查詢條件中的查詢欄位不一定是有索引的,那么就會存在嚴重的查詢性能問題,另外如果UNION的表過多,即使不都是大表也仍然存在查詢性能問題,而且查詢性能隨著UNION的表的數量增加查詢性能而降低,導致無法擴展,

? 這里有人可能會說,分頁查詢一般都是單表或JOIN多表的結果集,即使UNION多張表也不會太多,為何要考慮擴展?我只能說,一切皆有可能,誰也沒有規定分頁查詢只能單表或限定在幾張表內,如果產品經理提出需要將多個功能模塊(對于開發人員來說:可能是多張表)的資料合并分頁查詢展示,那我們也必需實作,斷然不能因為“實作不了 或 實作有難度 或 存在性能問題”就拒絕此類需求,因為產品經理提出的需求肯定有他的背景及業務價值,作為開發人員,且想做為一個優秀的開發人員,那么“有求必應”是必備的作業態度,豪不夸張的張,沒有實作不了的產品需求,就看實作的成本(包含時間成本、人力成本、物質成本等)是否與產品需求的價值相匹配,如果成本與價值基本相符(或說投入與產出后的效果),那么即使再難實作也必定是可以實作的,扯得有點遠了,還是回到上面所描述的關于多張表分頁查詢的問題,UNION多張表確實可以解決一些相對簡單的多表分頁的問題,但如果多張表的資料欄位結構、記錄數不相同(即:欄位名不同、一對多、單行水平欄位、垂直多行欄位),甚至不僅僅是多張表,有可能是跨系統、跨DB的多張表或是動態計算的結果,這些情況下,UNION SQL的方式肯定是滿足不了了,必需要有其它的解決辦法,我認為最好的實作方式有兩種:一種是想辦法將多查詢來源(因為不僅限于表)的記錄全部匯總到一張總表上,然后使用傳統的單表分頁查詢SQL即可(正如一開始所舉例的SQL),另一種就是本文重點介紹的,支持多資料源分頁查詢工具類(MultiSourcePageQueryBuilder)

多資料源分頁查詢工具類(MultiSourcePageQueryBuilder)介紹

多資料源分頁查詢工具類(MultiSourcePageQueryBuilder)的使用前提條件是:多個查詢來源(不僅限于表)必需是有順序的,即:先查第1個來源,查完后再查下一個來源,依此類推,直至查完所有來源,分頁結束,如:表1,表2,表3,先分頁查表1,查完后再查表2,查完后最后查表3,

多資料源分頁查詢工具類(MultiSourcePageQueryBuilder)的使用效果:多個查詢來源(不僅限于表)能夠正常記錄總頁數,總記錄數,能夠支持正常連續分頁,跳轉分頁,且只要不是最后1頁,則每頁的記錄數均為設定的頁大小(即:pageSize,滿頁),若上一個查詢來源的記錄數不足頁大小則會自動切換下一個查詢來源來補足1頁大小的記錄,否則最后1頁才有可能剩余記錄不足1頁大小的情況(即:與傳統單表分頁查詢效果一致),整體對前端,對用戶無差異感知,

多資料源分頁查詢工具類(MultiSourcePageQueryBuilder)的實作原理與機制:

  1. 先通過匯總計算每個查詢來源的總記錄數,然后根據每個查詢來源的總記錄數精確計算出分頁區間占比情況(即:pageRange),分頁區間的關鍵資訊有:開始區間頁碼、結束區間頁碼、所屬查詢來源、開始頁實際記錄數、結束頁記錄數(注意:結束頁記錄數是累加的,目的是便于計算下一個查詢來源的分頁區間),最后得出真實的總頁數、總記錄數;(對應代碼的方法:getTotalCountResult),下面通過一個表格來展示分頁區間的計算情況:

    假設:pageSize:每頁2條

    如下每一單元格代表一行記錄數,單元格中的數字表示分頁數字,不同顏色區分不同的查詢來源

  2. 分頁查詢時,根據前端用戶選擇的查詢頁碼、查詢來源(這個首次不傳則為默認0,后面若跨查詢來源則會由后端回傳給前端,前端保存)、分頁大小、分頁區間(這個由后端計算后回傳給前端保存)等入參資訊(MultiSourcePagination),先由頁碼獲得分頁區間物件串列(不足1頁跨多查詢來源時會有多個查詢來源,否則一般都只會命中一個分頁區間),選擇第1個分頁區間物件,若這個分頁區間的查詢來源與當前請求的查詢來源相同說明是正常的分頁,則執行正常分頁邏輯;若不相同時說明存在跳頁情況,則再判斷當前查詢的頁碼是否為這個分頁區間對應的的開始頁碼,若是說明無需分隔點,則僅需切換查詢來源及設定查詢來源的分頁超始頁碼后執行正常分頁邏輯,否則說明跳頁且當前查詢的頁碼在這個查詢來源的第2頁及后面的分頁區間內(含最末頁)或分頁區間開始頁存在跨多個查詢來源(即:多個查詢來源補足1頁記錄,如:表1占10條,表2占10條,頁大小為20條),此時就需要先根據分頁區間的開始頁記錄數及查詢條件查出對應的補頁記錄資訊,然后獲取結果的最后一條記錄作為這個查詢來源的分頁過濾條件(注意:若查詢補頁記錄后的資料源與當前原請求的分頁區間的資料源不相同時,則說明資料有變化(資料條數變少或沒有,導致切換下一個查詢來源),此時應重新匯總計算分頁資訊,以便再翻頁時能準確查詢到資料),最后執行正常分頁邏輯(對應代碼的方法:getPageQueryResult)

  3. 正常分頁邏輯(對應代碼的方法:doPageQueryFromMultiSource):根據請求的查詢來源索引從已設定的多資料源分頁查詢回呼方法串列中選擇對應的分頁查詢回呼方法參考,執行分頁查詢回呼方法獲得分頁的結果,若結果記錄滿足頁大小(即:實際記錄數=頁大小pageSize)則正常回傳即可,否則需判斷是否為最后一個查詢來源,若是則說明已到最大頁碼,直接回傳當前剩余記錄即可,無需補充分頁記錄的情況,除外則說明查詢的結果為慷訓記錄數不滿1頁大小,需要跨查詢來源進行補頁查詢(即:缺少幾條記錄就查多少記錄補全1頁大小,如:頁大小20,表1查詢出8條,不足1頁還差12條,則切換查表2查詢12條補全1頁),注意可能存在跨多個查詢來源才補全1頁大小的情況,最后在回傳分頁結果時,需將補頁記錄的最后一條記錄設定為查詢來源的分頁過濾條件(querySourceFilterStart)、當前請求頁碼設定為這個查詢來源的分頁起始頁碼(即:已占用的頁碼,querySourcePageStart)一并回傳給前端即可,后續翻頁時前端除了更改頁碼外還需將上述分頁區間資訊、分頁過濾條件、分頁起始頁碼等回傳給后端,以避免每次都要后端重新計算 影響查詢性能或因分頁入參資訊不全不準導致分頁結果不正確的情況;

    下面通過表格圖來展示幾種情況下的多資料源的分頁情況

    其中:pageLimitStart=(this.page【請求的頁碼】 - this.querySourcePageStart【起始頁碼】 - 1) * this.pageSize【頁大小】;

    第一種情況:無論是正常分頁(即:連續分頁)或是跳頁分頁(即:隨機頁碼翻頁)均不存在補頁情況(即:同1頁中包含多個查詢來源的資料),最為簡單,每個查詢來源均正常分頁查詢即可(limit pageLimitStart,pageSize),跳頁時僅需確認查詢來源、分頁起始頁碼即可;

第二種情況:無論是正常分頁(即:連續分頁)或是跳頁分頁(即:隨機頁碼翻頁)均需要補頁情況,由于涉及補頁的情況,故跳頁時也分兩種情況,如果在已執行過的查詢來源的分頁區間中進行跳頁(情形1),那么僅需確定查詢來源、分頁起始頁碼即可,而如果從一個已執行過的查詢來源跳到未執行過的查詢來源(情形2),那么此時因為存在補頁故必需先查詢這個查詢來源的分頁區間起始頁補頁記錄資訊從而確定分隔過濾條件及分頁起始頁碼;

? 第三種情況:與上面第二種情況一下,無論是正常分頁(即:連續分頁)或是跳頁分頁(即:隨機頁碼翻頁)均需要補頁情況,但補頁涉及多個查詢來源;

總之:不論哪種情況,如果某個查詢來源不足1頁大小時,必需由另一個或多個查詢來源的記錄補全1頁,一旦存在補頁,那么補頁的最后查詢來源后面的頁碼記錄均需要排除掉補頁的記錄(這也就是為什么跳頁時,需要先查分頁區間的起始頁的補頁記錄并確認分隔點過濾條件的目的),即:需確認分隔過濾條件;

多資料源分頁查詢工具類(MultiSourcePageQueryBuilder)代碼快速上手示例指南:

示例1:(這里采用的是純記憶體模擬資料,其實也說明了支持不同型別的查詢來源,不論是DB的表或記憶體中的集合物件 、甚至是調外部系統的介面,只要能符合分頁的出入參欄位即可,混合也是可以的)

@RunWith(JUnit4.class)
public class MultiSourcePageQueryBuilderTests {

    @Test
    public void testPageQuery() {

        //構建3張虛擬表的記錄(假設現在有3張表)
        final List<ATable> table1 = new ArrayList<>();
        table1.add(new ATable(1, "zs", new Timestamp(System.currentTimeMillis()), 202112));
        table1.add(new ATable(2, "zs2", new Timestamp(System.currentTimeMillis()), 202110));
        table1.add(new ATable(3, "zs3", new Timestamp(System.currentTimeMillis()), 202201));
        table1.add(new ATable(4, "zs4", new Timestamp(System.currentTimeMillis()), 202202));
        table1.add(new ATable(5, "zs5", new Timestamp(System.currentTimeMillis()), 202203));


        final List<ATable> table2 = new ArrayList<>();
        table2.add(new ATable(1, "ls", new Timestamp(System.currentTimeMillis()), 202111));
        table2.add(new ATable(2, "ls2", new Timestamp(System.currentTimeMillis()), 202112));
        table2.add(new ATable(3, "ls3", new Timestamp(System.currentTimeMillis()), 202202));
        table2.add(new ATable(4, "ls4", new Timestamp(System.currentTimeMillis()), 202202));
        table2.add(new ATable(5, "ls5", new Timestamp(System.currentTimeMillis()), 202203));

        final List<ATable> table3 = new ArrayList<>();
        table3.add(new ATable(11, "ww", new Timestamp(System.currentTimeMillis()), 202111));
        table3.add(new ATable(22, "ww2", new Timestamp(System.currentTimeMillis()), 202112));
        table3.add(new ATable(33, "ww3", new Timestamp(System.currentTimeMillis()), 202203));
        table3.add(new ATable(44, "ww4", new Timestamp(System.currentTimeMillis()), 202202));
        table3.add(new ATable(55, "ww5", new Timestamp(System.currentTimeMillis()), 202203));


        MultiSourcePageQueryBuilder<ATable,ATable> pageQueryBuilder = new MultiSourcePageQueryBuilder<>();
        pageQueryBuilder.addCountQuerySources(pagination -> {
            //這里僅為演示,現實是查表1 SQL COUNT
            return table1.stream().count();
        }).addCountQuerySources(pagination -> {
            //這里僅為演示,現實是查表2 SQL COUNT
            return table2.stream().count();
        }).addCountQuerySources(pagination -> {
            //這里僅為演示,現實是查表3 SQL COUNT
            return table3.stream().count();

            //如果COUNT與實際分頁分開,則可以在不同的地方按需進行組合,但注意:若同時存在addCountQuerySources、 addPageQuerySources,則他們必需配對(即:count與pageQuery的集合索引一致)
        }).addPageQuerySources(pagination -> {
            //這里僅為演示,現實是查表1 分頁SQL(基于limit分頁)
            return doPageQuery(pagination, table1);

        }).addPageQuerySources(pagination -> {
            //這里僅為演示,現實是查表2 分頁SQL(基于limit分頁)
            return doPageQuery(pagination, table2);
        }).addPageQuerySources(pagination -> {
            //這里僅為演示,現實是查表3 分頁SQL(基于limit分頁)
            return doPageQuery(pagination, table3);
        });

        MultiSourcePagination<ATable,ATable> pagination = new MultiSourcePagination<>();
        pagination.setPageSize(7);
        pagination.setPage(1);
        pagination.setQueryCriteria(new GenericBO<ATable>());
        MultiSourcePagination<ATable,ATable> paginationResult = pageQueryBuilder.getTotalCountResult(pagination);
        System.out.println("total result:" + JsonUtils.deserializer(paginationResult));


        while (true) {
            paginationResult = pageQueryBuilder.getPageQueryResult(pagination);
            if (paginationResult == null || CollectionUtils.isEmpty(paginationResult.getRows())) {
                break;
            }
            System.out.printf("page:%d, list:%s, %n", paginationResult.getPage(), JsonUtils.deserializer(paginationResult));
            //因為是模擬測驗,每次的結果必需清除掉
            paginationResult.setRows(null);
            paginationResult.setPage(paginationResult.getPage() + 1);//模擬跳頁
        }

        System.out.printf("page end:%d %n", paginationResult.getPage());

        Assert.assertEquals(3,paginationResult.getPageTotal());

    }

    private List<ATable> doPageQuery(MultiSourcePagination<ATable,ATable> pagination, List<ATable> tableX) {
        if (pagination.getLimitRowCount() > 0) {
            //補充分頁(無分隔點)
            return tableX.stream()
                    .sorted((o1, o2) -> (o1.inMonth >= o2.inMonth && o1.id > o2.id) ? 1 : ((o1.inMonth == o2.inMonth && o1.id == o2.id) ? 0 : -1))
                    .limit(pagination.getLimitRowCount()).collect(Collectors.toList());
        } else if (pagination.getQuerySourceFilterStart() != null) {
            //正常分頁(有分隔點)
            return tableX.stream()
                    .filter(t -> t.id > pagination.getQuerySourceFilterStart().getId() && t.inMonth >= pagination.getQuerySourceFilterStart().getInMonth())
                    .sorted((o1, o2) -> (o1.inMonth >= o2.inMonth && o1.id > o2.id) ? 1 : ((o1.inMonth == o2.inMonth && o1.id == o2.id) ? 0 : -1))
                    .skip(pagination.getPageLimitStart()).limit(pagination.getPageSize()).collect(Collectors.toList());
        } else {
            //正常分頁
            return tableX.stream()
                    .sorted((o1, o2) -> (o1.inMonth >= o2.inMonth && o1.id > o2.id) ? 1 : ((o1.inMonth == o2.inMonth && o1.id == o2.id) ? 0 : -1))
                    .skip(pagination.getPageLimitStart()).limit(pagination.getPageSize()).collect(Collectors.toList());
        }
    }


    private static class ATable {
        private int id;
        private String name;
        private Timestamp updateDate;
        private long inMonth;

        public ATable(int id, String name, Timestamp updateDate, long inMonth) {
            this.id = id;
            this.name = name;
            this.updateDate = updateDate;
            this.inMonth = inMonth;
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Timestamp getUpdateDate() {
            return updateDate;
        }

        public void setUpdateDate(Timestamp updateDate) {
            this.updateDate = updateDate;
        }

        public long getInMonth() {
            return inMonth;
        }

        public void setInMonth(long inMonth) {
            this.inMonth = inMonth;
        }
    }
}

示例2:(mybatis spring boot)

相關mapper xml的SQL定義片段:

<!-- AMapper.xml sql:-->

    <select id="selectCount" resultType="long" parameterType="cn.zuowenjun.model.MultiSourcePagination">
        select count(1) from tableA a inner join tableB b on a.b_id=b.id
        inner join tableC c on b.id=c.b_id
        where a.enabled_flag=1 and b.enabled_flag=1 and c.enabled_flag=1
        and b.in_month=#{queryCriteria.vo.inMonth} and c.un_receive_fee_amount>0
    </select>

    <select id="pageLimitQuery" resultType="cn.zuowenjun.model.AUnPaidInfo" parameterType="cn.zuowenjun.model.MultiSourcePagination">
        select b.in_month as 'inMonth',b.id as 'bizRowId',a.bill_number as 'bizNumber',c.un_receive_fee_amount as 'unPaidAmount','M' as 'bizType'
       from tableA a inner join tableB b on a.b_id=b.id
        inner join tableC c on b.id=c.b_id
        where a.enabled_flag=1 and b.enabled_flag=1 and c.enabled_flag=1
        and b.in_month=#{queryCriteria.vo.inMonth} and c.un_receive_fee_amount>0

        <if test="querySourceFilterStart!=null">
            <![CDATA[
               and b.id > #{querySourceFilterStart.bizRowId} and b.in_month>=#{querySourceFilterStart.inMonth}
           ]]>
        </if>
        order by b.in_month,b.id asc
        <choose>
            <when test="limitRowCount>0">
                limit #{limitRowCount}
            </when>
            <otherwise>
                limit #{pageLimitStart},#{pageSize}
            </otherwise>
        </choose>

    </select>

<!-- BMapper.xml sql:-->

<select id="selectCount" resultType="long" parameterType="cn.zuowenjun.model.MultiSourcePagination">
        select count(1)
        from tableAA a inner join tableBB b on a.b_id=b.id
        inner join tableCC c on b.id=c.b_id
        where a.enabled_flag=1 and b.enabled_flag=1 and c.enabled_flag=1
        and b.in_month=#{queryCriteria.vo.inMonth} and c.un_receive_fee_amount>0
    </select>

    <select id="pageLimitQuery" resultType="cn.zuowenjun.model.AUnPaidInfo" parameterType="cn.zuowenjun.model.MultiSourcePagination">
        select b.in_month as 'inMonth',b.id as 'bizRowId',a.waybill_number as 'bizNumber',c.un_receive_fee_amount as 'unPaidAmount','P' as 'bizType'
         from tableAA a inner join tableBB b on a.b_id=b.id
        inner join tableCC c on b.id=c.b_id
        where a.enabled_flag=1 and b.enabled_flag=1 and c.enabled_flag=1
        and b.in_month=#{queryCriteria.vo.inMonth} and c.un_receive_fee_amount>0

        <if test="querySourceFilterStart!=null">
            <![CDATA[
               and b.id > #{querySourceFilterStart.bizRowId} and b.in_month>=#{querySourceFilterStart.inMonth}
           ]]>
        </if>
        order by b.in_month,b.id asc
        <choose>
            <when test="limitRowCount>0">
                limit #{limitRowCount}
            </when>
            <otherwise>
                limit #{pageLimitStart},#{pageSize}
            </otherwise>
        </choose>

    </select>


<!-- CMapper.xml sql:-->

    <select id="selectCount" resultType="long" parameterType="cn.zuowenjun.model.MultiSourcePagination">
        select count(1) from tableC where uncollect_amount>0 and enabled_flag=1 and in_month=#{queryCriteria.vo.inMonth}
    </select>

    <select id="pageLimitQuery" resultType="cn.zuowenjun.model.AUnPaidInfo" parameterType="cn.zuowenjun.model.MultiSourcePagination">
        select in_month as 'inMonth',id as 'bizRowId',bill_number as 'bizNumber',uncollect_amount as 'unPaidAmount','O' as 'bizType'
        from tableC where uncollect_amount>0 and enabled_flag=1 and in_month=#{queryCriteria.vo.inMonth}
        <if test="querySourceFilterStart!=null">
            <![CDATA[
               and id > #{querySourceFilterStart.bizRowId} and in_month>=#{querySourceFilterStart.inMonth}
           ]]>
        </if>
        order by in_month,id asc
        <choose>
            <when test="limitRowCount>0">
                limit #{limitRowCount}
            </when>
            <otherwise>
                limit #{pageLimitStart},#{pageSize}
            </otherwise>
        </choose>

    </select>

JAVA代碼片段:


//前置作業:

// AMapper.java、BMapper.java、CMapper.java 3個mapper 介面類中均定義如下兩個方法
	//計算總記錄數(命名不固定)
	Long selectCount(MultiSourcePagination<AUnPaidInfo,AUnPaidInfo> pagination);
    //分頁查詢(命名不固定)
	List<AUnPaidInfo> pageLimitQuery(MultiSourcePagination<AUnPaidInfo,AUnPaidInfo> pagination);


//對應的AService.java 、BService.java、CService.java 均定一個如下示例的獲取上述mapper的方法,當然也可以照上面的mapper方法在對應的Service類中定義對應的方法,內部仍然直接調mapper介面類的方法(實際是mapper proxy的代理方法)
	private AMapper aMapper=null;
    public BillMonthlyService(@Autowired AMapper aMapper) {
        this.aMapper=aMapper;
    }

    public BillMonthlyMapper getMapper() {
        return aMapper;
    }

//真正的分頁使用:(這里使用介面)
@RestController
public class TestController {
    @Autowired
    private AService aService;
    @Autowired
    private BService bService;
    @Autowired
    private CService cService;

    @ApiOperation("測驗多資料源分頁查詢")
    @RequestMapping(value = "https://www.cnblogs.com/test/pageQueryUnPaids",method = RequestMethod.POST)
    public MultiSourcePagination<AUnPaidInfo,AUnPaidInfo> pageQueryUnPaids(@RequestBody MultiSourcePagination<AUnPaidInfo,AUnPaidInfo> request){

        if (request==null || request.getQueryCriteria()==null || request.getQueryCriteria().getVo()==null){
            throw new RuntimeException("入參不能為空!");
        }

        MultiSourcePageQueryBuilder<AUnPaidInfo,AUnPaidInfo> pageQueryBuilder=new MultiSourcePageQueryBuilder<>();
       //addCountQuerySources、addPageQuerySources 是支持鏈式呼叫,為了便于
        pageQueryBuilder.addCountQuerySources(r->aService.getMapper().selectCount(r))
                .addPageQuerySources(r->aService.getMapper().pageLimitQuery(r))

                .addCountQuerySources(r->bService.getMapper().selectCount(r))
                .addPageQuerySources(r->bService.getMapper().pageLimitQuery(r))

                .addCountQuerySources(r->cService.getMapper().selectCount(r))
                .addPageQuerySources(r->cService.getMapper().pageLimitQuery(r));

        return pageQueryBuilder.getPageQueryResult(request);
    }
}


//出參資訊物件(這里也作為入參對明)
public class AUnPaidInfo implements Serializable {
    private static final long serialVersionUID = 1L;

    private String inMonth;
    private String bizType;
    private String bizNumber;
    private Double unPaidAmount;
    private Long bizRowId;

    public String getInMonth() {
        return inMonth;
    }

    public void setInMonth(String inMonth) {
        this.inMonth = inMonth;
    }

    public String getBizType() {
        return bizType;
    }

    public void setBizType(String bizType) {
        this.bizType = bizType;
    }

    public String getBizNumber() {
        return bizNumber;
    }

    public void setBizNumber(String bizNumber) {
        this.bizNumber = bizNumber;
    }

    public Double getUnPaidAmount() {
        return unPaidAmount;
    }

    public void setUnPaidAmount(Double unPaidAmount) {
        this.unPaidAmount = unPaidAmount;
    }

    public Long getBizRowId() {
        return bizRowId;
    }

    public void setBizRowId(Long bizRowId) {
        this.bizRowId = bizRowId;
    }
}

好了,最后就貼出MultiSourcePageQueryBuilder 源代碼,其實理解了多資料源分頁的原理后寫代碼還是很簡單的,

public class MultiSourcePageQueryBuilder<T,B> {
    private final List<Function<MultiSourcePagination<T,B>, Long>> countQuerySourceFuncList = new ArrayList<>();
    private final List<Function<MultiSourcePagination<T,B>, List<T>>> pageQuerySourceFuncList = new ArrayList<>();

    /**
     * 添加需要進行多查詢來源(即:多表)計算總記錄數的回呼方法,同時支持一次性寫多個也可以鏈式添加
     *
     * @param countQuerySourceFuncArr count SQL對應的service方法,SQL類似如下:
     *                                <pre>
     *                                 select count(1) from table where 查詢條件
     *                                               </pre>
     * @return
     */
    @SafeVarargs
    public final MultiSourcePageQueryBuilder<T,B> addCountQuerySources(Function<MultiSourcePagination<T,B>, Long>... countQuerySourceFuncArr) {
        Assert.notEmpty(countQuerySourceFuncArr, "請指定需要計算總記錄數的回呼方法【每個查詢來源1個方法】!");
        this.countQuerySourceFuncList.addAll(Arrays.asList(countQuerySourceFuncArr));
        return this;
    }

    /**
     * 添加需要進行多查詢來源(即:多表)分頁查詢的回呼方法,同時支持一次性寫多個也可以鏈式添加
     *
     * @param pageQuerySourceFuncArr 分頁查詢(含補充查詢) SQL對應的service方法,SQL類似如下:(假設按in_month,id排序分頁)
     *                               <pre>
     *                                  <select id="pageLimitQuery">
     *                                     select * from tableX where enabled_flag=1 and 查詢條件...
     *                                     <if test="querySourceFilterStart!=null">
     *                                         <![CDATA[
     *                                         and id > #{querySourceFilterStart.id} and in_month>=#{querySourceFilterStart.inMonth}
     *                                         ]]>
     *                                     </if>
     *                                     order by in_month,id asc
     *                                     <choose>
     *                                         <when test="limitRowCount>0">
     *                                             limit #{limitRowCount}
     *                                         </when>
     *                                         <otherwise>
     *                                             limit #{pageLimitStart},#{pageSize}
     *                                         </otherwise>
     *                                     </choose>
     *                                  </select>
     *                                 </pre>
     * @return
     */
    @SafeVarargs
    public final MultiSourcePageQueryBuilder<T,B> addPageQuerySources(Function<MultiSourcePagination<T,B>, List<T>>... pageQuerySourceFuncArr) {
        this.pageQuerySourceFuncList.addAll(Arrays.asList(pageQuerySourceFuncArr));
        return this;
    }


    /**
     * 獲取最終合并計算的總記錄數、總頁數結果資訊
     *
     * @param paginationRequest
     * @return
     */
    public final MultiSourcePagination<T,B> getTotalCountResult(MultiSourcePagination<T,B> paginationRequest) {
        Assert.notEmpty(countQuerySourceFuncList, "請指定需要計算總記錄數的回呼方法【每個查詢來源1個方法】!");
        paginationRequest.setPageRanges(new ArrayList<>());
        paginationRequest.setRowTotal(0);
        paginationRequest.setPageTotal(0);
        for (int i = 0; i < countQuerySourceFuncList.size(); i++) {
            Function<MultiSourcePagination<T,B>, Long> countQuerySourceFunc = countQuerySourceFuncList.get(i);
            MultiSourcePagination.SourcePageRange sourcePageRange = null;
            int rowTotalCount = countQuerySourceFunc.apply(paginationRequest).intValue();
            if (rowTotalCount == 0) {
                continue;
            }

            if (CollectionUtils.isEmpty(paginationRequest.getPageRanges())) {
                //如果是第1個有記錄的查詢來源,即開始
                if (rowTotalCount <= paginationRequest.getPageSize()) {
                    //如果總記錄數不足1頁,直接設定頁區間范圍
                    sourcePageRange = new MultiSourcePagination.SourcePageRange(1, 1, i, rowTotalCount, rowTotalCount);
                } else {
                    //否則正常計算總頁數及剩余頁的記錄數
                    int pageTotal = (rowTotalCount / paginationRequest.getPageSize()) + (rowTotalCount % paginationRequest.getPageSize() > 0 ? 1 : 0);
                    int remainEndPageSize = rowTotalCount - (rowTotalCount / paginationRequest.getPageSize()) * paginationRequest.getPageSize();
                    sourcePageRange = new MultiSourcePagination.SourcePageRange(1, 1 + pageTotal - 1, i, paginationRequest.getPageSize(), remainEndPageSize>0?remainEndPageSize:paginationRequest.getPageSize());
                }
            } else {
                //獲取上一個查詢來源的分頁區間資訊
                MultiSourcePagination.SourcePageRange preSourcePageRange = paginationRequest.getPageRanges().get(paginationRequest.getPageRanges().size() - 1);
                //補頁記錄
                int mergeSize = paginationRequest.getPageSize() - preSourcePageRange.getEndPageSize();
                //剩余可分頁記錄(減去補頁記錄)
                int remainSize = rowTotalCount - mergeSize;
                //整數頁數
                int fullPageCount =0;
                //余頁記錄數(不足1頁的記錄)
                int remainEndPageSize=0;
                //總頁數
                int pageTotal=0;
                //開始頁的實際條數(如果有補頁,則實際補頁記錄為開始頁的條數,否則記錄數超過1頁,則為頁大小,否則實際記錄數【不足1頁】)
                int beginPageSize = mergeSize > 0 && remainSize > 0 ? mergeSize : (mergeSize == 0 && remainSize >= paginationRequest.getPageSize() ? paginationRequest.getPageSize() : rowTotalCount);
                if (remainSize > 0) {
                    fullPageCount = remainSize / paginationRequest.getPageSize();
                    remainEndPageSize = remainSize - fullPageCount * paginationRequest.getPageSize();
                    pageTotal = fullPageCount + (remainEndPageSize > 0 ? 1 : 0);
                } else {
                    //如果剩余可分頁記錄數<=0,則說明無法補完或剛好僅補完1頁,則當頁即為最后頁
                    remainEndPageSize = remainSize < 0 ? preSourcePageRange.getEndPageSize() + rowTotalCount : paginationRequest.getPageSize();
                    //無法補完或剛好僅補完1頁時,則此時第1頁的有效記錄數則為實際的記錄
                    beginPageSize = rowTotalCount;
                }
                //開始頁碼
                int beginPage = preSourcePageRange.getEndPage() + 1;
                if (mergeSize > 0) {
                    //如果有補頁記錄,則開始頁與上一個查詢來源結束頁有交集,需設定為上一個查詢來源的結束頁碼
                    beginPage = preSourcePageRange.getEndPage();
                    //有補頁,實際總頁數也得加1
                    pageTotal+=1;
                }

                sourcePageRange = new MultiSourcePagination.SourcePageRange(beginPage, beginPage + pageTotal - 1, i, beginPageSize, remainEndPageSize>0?remainEndPageSize:paginationRequest.getPageSize());
            }

            paginationRequest.setRowTotal(paginationRequest.getRowTotal() + rowTotalCount);
            paginationRequest.getPageRanges().add(sourcePageRange);
        }

        if (paginationRequest.getRowTotal() > 0) {
            //如果有記錄,則總頁數=最后一個查詢來源的頁區間的結束頁碼
            paginationRequest.setPageTotal(paginationRequest.getPageRanges().get(paginationRequest.getPageRanges().size()-1).getEndPage());
        }

        return paginationRequest;
    }

    /**
     * 獲取最終合并分頁的結果資訊
     *
     * @param paginationRequest
     * @return
     */
    public final MultiSourcePagination<T,B> getPageQueryResult(MultiSourcePagination<T,B> paginationRequest) {
        Assert.notEmpty(pageQuerySourceFuncList, "未設定分頁查詢回呼方法,請先通過addPageQuerySources方法進行設定!");
        Assert.notNull(paginationRequest, "查詢條件不能為空!");
        if (paginationRequest.isCount() || paginationRequest.getPageTotal()<=0) {
            //如果需要匯總計算總記錄數、總頁數(含之前沒有匯總計算過),則需先進行匯總計算
            getTotalCountResult(paginationRequest);
        }

        //begin 這個代碼塊主要是根據當前頁碼確定對應的查詢來源的分頁區間,根據分頁查詢決定如何切換查詢來源及分隔點資訊
        List<MultiSourcePagination.SourcePageRange> currentSourcePageRanges = getCurrentSourcePageRanges(paginationRequest);
        if (!CollectionUtils.isEmpty(currentSourcePageRanges)) {
            //如果查出多個分頁區間,則說明當前頁碼在開始頁或結束頁交集中(若無交集,只會有1條),此時取頁交集頁中的第1查詢來源;若只有1個分頁區間,則正常分頁即可
            MultiSourcePagination.SourcePageRange currentSourcePageRange=currentSourcePageRanges.get(0);
            if (currentSourcePageRange != null && currentSourcePageRange.getSource() != paginationRequest.getQuerySource()) {
                paginationRequest.setQuerySourceFilterStart(null);
                //說明有跳轉頁碼,且跨查詢來源,則需要先根據對應的查詢來源查所在的分頁區間的開始頁
                if (paginationRequest.getPage() == currentSourcePageRange.getBeginPage() || currentSourcePageRange.getBeginPageSize() == paginationRequest.getPageSize()) {
                    //如果是切換查詢來源,但剛好是這個查詢來源分頁區間的第1頁 或這個查詢來源開始頁是整頁(即:不存在補頁),則僅切換查詢來源即可,因為分頁查詢中會正常查詢,不足1頁也會自動切換查詢來源
                    paginationRequest.setQuerySource(currentSourcePageRange.getSource());
                    paginationRequest.setQuerySourcePageStart(currentSourcePageRange.getBeginPage() - (currentSourcePageRange.getBeginPageSize() == paginationRequest.getPageSize() ? 1 : 0));
                } else {
                    //如果是切換查詢來源,且頁碼在這個查詢來源的第2頁及后面的分頁區間內(含最末頁)或1頁跨多個查詢來源,則必需先查詢這個來源的分頁區間的開始頁碼資料,以便確定跨來源的分隔點
                    queryBeginPageBySource(paginationRequest, currentSourcePageRange);
                }
            }
        }
        // end

        return doPageQueryFromMultiSource(paginationRequest);
    }

    private void queryBeginPageBySource(MultiSourcePagination<T,B> paginationRequest, MultiSourcePagination.SourcePageRange sourcePageRange) {
        MultiSourcePagination<T,B> newPagination = new MultiSourcePagination<>();
        newPagination.setPageRanges(paginationRequest.getPageRanges());
        newPagination.setLimitRowCount(sourcePageRange.getBeginPageSize());
        newPagination.setPageSize(sourcePageRange.getBeginPageSize());
        newPagination.setQuerySource(sourcePageRange.getSource());
        newPagination.setQueryCriteria(paginationRequest.getQueryCriteria());

        //獲取當前查詢來源的分頁區間的起始頁資訊(僅補頁時需要),以便獲得分頁的條件過濾起點、頁碼起點等
        //類似執行SQL:select * from table2 where 查詢條件 order by 分頁排序欄位 limit #{LimitRowCount}
        MultiSourcePagination<T,B> paginationResponse = doPageQueryFromMultiSource(newPagination);

        paginationRequest.setQuerySource(paginationResponse.getQuerySource());
        paginationRequest.setQuerySourcePageStart(sourcePageRange.getBeginPage() - (sourcePageRange.getBeginPageSize() == paginationRequest.getPageSize() ? 1 : 0));

        if (CollectionUtils.isEmpty(paginationResponse.getRows())){
            return;
        }

        //回填:資料源、頁碼起點(setQuerySource\setQuerySourcePageStart)、條件過濾起點(setQuerySourceFilterStart),以確保在這個查詢來源內的跳轉分頁查詢正常 【即:確定補頁的最后1條記錄資訊,以便后面的分頁帶上這個分隔條件,排除補頁的記錄】
        paginationRequest.setQuerySourceFilterStart(paginationResponse.getRows().get(paginationResponse.getRows().size()-1));

        if (paginationRequest.getQuerySource() != sourcePageRange.getSource() && !CollectionUtils.isEmpty(countQuerySourceFuncList)) {
            //如果查詢來源的分頁區間的首頁資料源與原分頁區間的資料源不相同,說明資料有變化(資料條數變少或沒有,導致切換下一個查詢來源),則此時應重新匯總計算分頁資訊
            getTotalCountResult(paginationRequest);
            List<MultiSourcePagination.SourcePageRange> currentSourcePageRanges = getCurrentSourcePageRanges(paginationRequest);
            if (CollectionUtils.isEmpty(currentSourcePageRanges)){
                //正常一定會匹配到,若匹配不到,說明記錄數變少了,少到小于當前頁碼的記錄,這種則正常回傳
                return;
            }

            paginationRequest.setQuerySourcePageStart(currentSourcePageRanges.get(0).getBeginPage() - (currentSourcePageRanges.get(0).getBeginPageSize() == paginationRequest.getPageSize() ? 1 : 0));
        }
    }


    /**
     * 執行具體的多查詢來源的合并分頁邏輯
     *
     * @param paginationRequest
     * @return
     */
    private MultiSourcePagination<T,B> doPageQueryFromMultiSource(MultiSourcePagination<T,B> paginationRequest) {
        if (paginationRequest.getQuerySource() + 1 > pageQuerySourceFuncList.size()) {
            //如果查詢來源索引值超過設定的分頁查詢來源回呼方法集合,則說明入參不正確,直接回傳
            return paginationRequest;
        }

        Function<MultiSourcePagination<T,B>, List<T>> currentPageQueryFunc = pageQuerySourceFuncList.get(paginationRequest.getQuerySource());
        List<T> pagedList = currentPageQueryFunc.apply(paginationRequest);

        if (!CollectionUtils.isEmpty(pagedList)) {
            if (CollectionUtils.isEmpty(paginationRequest.getRows())) {
                //如果不存在記錄,則直接設定結果記錄
                paginationRequest.setRows(pagedList);
            } else {
                //如果已存在記錄,說明此處為補充查詢,則合并結果記錄
                paginationRequest.getRows().addAll(pagedList);
            }
            if (paginationRequest.getRows().size() >= paginationRequest.getPageSize()) {
                //查詢結果(含補充的)滿1頁,則正常回傳
                return paginationRequest;
            }
        }

        if (paginationRequest.getQuerySource() + 1 >= pageQuerySourceFuncList.size()) {
            //查詢結果不滿1頁(或為空),但已是最后的查詢來源(即:最后一張表),則說明已到最大頁碼,直接回傳當前剩余記錄即可,無需補充分頁記錄的情況
            //此時不用總頁數與頁碼判斷,是考慮資料本身就在動態變化,按查詢的實際結果即可
            paginationRequest.setRows(pagedList);
            return paginationRequest;
        }

        //除外,則說明查詢的結果為慷訓記錄數不滿1頁,則需要跨查詢來源(即:切換到另一個表進行查詢,補充分頁記錄)
        paginationRequest.setQuerySource(paginationRequest.getQuerySource() + 1);
        paginationRequest.setQuerySourceFilterStart(null);

        if (!CollectionUtils.isEmpty(pagedList)) {
            //若不滿1頁,則限制補充查詢剩余記錄數(注意可能多個查詢來源合并補充1頁,故這里是rows.size而不是pagedList.size)
            int offsetCount = paginationRequest.getPageSize() - paginationRequest.getRows().size();
            paginationRequest.setLimitRowCount(offsetCount);
        } else {
            //若查詢為空,則直接需要查詢完整的1頁
            paginationRequest.setLimitRowCount(paginationRequest.getPageSize());
        }

        //補充查詢下一個查詢來源(即:切換到下一個表進行補充查詢,如SQL:select * from table where 查詢條件 order by in_month,id limit #{limitRowCount})
        MultiSourcePagination<T,B> paginationResponse = doPageQueryFromMultiSource(paginationRequest);
        if (!CollectionUtils.isEmpty(paginationResponse.getRows())) {
            //設定下一頁查詢的分隔點-查詢過濾條件(實際下一頁的查詢來源的SQL查詢條件應加上querySourceLimitStart物件中的關鍵欄位,如SQL:where id>#{querySourceLimitStart.id} and in_month>=#{querySourceLimitStart.inMonth})
            paginationResponse.setQuerySourceFilterStart(paginationResponse.getRows().get(paginationResponse.getRows().size() - 1));
            //設定下一頁查詢的分隔點-已占用頁碼(實際下一頁的查詢來源的SQL頁碼應為:page-querySourcePageStart,如SQL:order by page-querySourcePageStart,pageSize )
            paginationResponse.setQuerySourcePageStart(paginationRequest.getPage());
        }
        //補充查詢完成后,將LimitRowCount還原默認值,以便下一次分頁請求時,可以正常進行分頁處理
        paginationResponse.setLimitRowCount(0);

        return paginationResponse;
    }

    private List<MultiSourcePagination.SourcePageRange> getCurrentSourcePageRanges(MultiSourcePagination<T,B> paginationRequest) {
        int page = paginationRequest.getPage();

        if (CollectionUtils.isEmpty(paginationRequest.getPageRanges())) {
            return null;
        }

        List<MultiSourcePagination.SourcePageRange> pageRanges = paginationRequest.getPageRanges().stream().filter(p -> p.getBeginPage() <= page && page <= p.getEndPage())
                .sorted(Comparator.comparingInt(MultiSourcePagination.SourcePageRange::getSource)).collect(Collectors.toList());

        return pageRanges;
    }


}


public class MultiSourcePagination<T,B> {
    //如下是本分頁欄位
    private int page = 1;
    private int pageSize;
    private List<T> rows;
    private int rowTotal;
    private int pageTotal;
    @JsonIgnore
    private boolean count=false;

    //如下是多資料源分頁所需欄位
    /**
     * 當前查詢來源索引(來源表索引,0,1,2...,默認為0)
     */
    private int querySource = 0;

    /**
     * 查詢來源【即:多表】頁碼分布區間資訊,以便快速根據page定位到對應的查詢來源
     */
    private List<SourcePageRange> pageRanges;

    /**
     * 查詢來源條件過濾起點(當存在跨查詢來源【即:跨表】補滿一頁記錄時則記錄當前頁最后的關鍵過濾條件物件資訊)
     */
    private T querySourceFilterStart;

    /**
     * 查詢來源的頁碼起點(當存在跨查詢來源【即:跨表】分頁時就記錄當前頁碼,默認為0)
     */
    private int querySourcePageStart = 0;

    /**
     * 限制行回傳的記錄數(即:limit N,僅在補充分頁時有值)
     */
    @JsonIgnore
    private int limitRowCount = 0;

    /**
     * 查詢條件
     */
    private B queryCriteria;

    public MultiSourcePagination() {
        //默認分頁程序中不匯總計算總記錄數、總頁數,以提高查詢性能,若有需要允許顯式設定為true
        this.count=false;
    }

    //省略getter、setter方法...

    /**
     * 獲取計算后的實際SQL limit start數值(當跨查詢來源【即:跨表】翻頁時,此值=page-querySourcePageStart,若還未發生跨查詢來源翻頁時,此值=page,因為querySourcePageStart=0【僅跨查詢來源時才有值】)
     *
     * @return 實際SQL limit start數值
     */
    @JsonIgnore
    public int getPageLimitStart() {

        if (this.page - this.querySourcePageStart - 1 <= 0) {
            return 0;
        }

        return (this.page - this.querySourcePageStart - 1) * this.pageSize;
    }

    public List<SourcePageRange> getPageRanges() {
        return pageRanges;
    }

    public void setPageRanges(List<SourcePageRange> pageRanges) {
        this.pageRanges = pageRanges;
    }

    /**
     * 查詢來源分頁區間資訊(即:每個查詢來源【即:表】實際對應的頁碼)
     */
    public static class SourcePageRange {
        /**
         * 開始頁碼
         */
        private final int beginPage;
        /**
         * 結束頁碼
         */
        private final int endPage;
        /**
         * 查詢來源索引
         */
        private final int source;

        /**
         * 開始頁實際記錄數
         */
        private final int beginPageSize;

        /**
         * 結束頁實際記錄數
         */
        private final int endPageSize;

        public SourcePageRange(int beginPage, int endPage, int source, int beginPageSize, int endPageSize) {
            this.beginPage = beginPage;
            this.endPage = endPage;
            this.source = source;
            this.beginPageSize = beginPageSize;
            this.endPageSize = endPageSize;
        }

        public int getBeginPage() {
            return beginPage;
        }

        public int getEndPage() {
            return endPage;
        }

        public int getSource() {
            return source;
        }

        public int getBeginPageSize() {
            return beginPageSize;
        }

        public int getEndPageSize() {
            return endPageSize;
        }
    }
}

提示:.NET語言也可以參考上述JAVA代碼轉為實作對應的C# 或VB.NET版本的多資料源分頁查詢工具類,個人覺得還是比較適用的,如果大家覺得也能幫助到你,可以點贊支持一下哈!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/442778.html

標籤:Java

上一篇:七、Java陣列

下一篇:pyhon爬蟲模擬網頁登陸、正則運算式

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more