前言

Github:https://github.com/HealerJean

博客:http://blog.healerjean.com

一、分页查询优化

问题 SQL

解释:表中的字段越多下面的优化越明显,否则即使使用了下面的优化,也可能没有那么明显

通过下面的可以观察到 当达到1000万的时候,查询时间到了37s,太可怕了

select * from tb_ams_inf_repay_stat limit 0,10 ; 
#  0.003s

select * from tb_ams_inf_repay_stat  limit 10000,10 ;  
# 1 0.023s

select * from tb_ams_inf_repay_stat  limit 100000,10 ;
# 10 0.191s

select * from tb_ams_inf_repay_stat limit 1000000,10 ;
# 100 1.942s

select * from tb_ams_inf_repay_stat limit 10000000,10 ;
# 1000 37.323s

1、简单优化

1)子查询

0.23s 简直要飞起来了

1、先使用覆盖索引 index 查询 ,我们只查询 id 索引这一个字段,比 select * 或者多个字段快多了,因为只要我们写上这些字段,我们只需要 10个,但是从第一条开始到 1000万条其实是都要去扫描的

2、然后再进行索引范围内 range查询

0.23s 
select *
from tb_ams_inf_repay_stat
where id > (select id from tb_ams_inf_repay_stat limit 1000000, 1)
limit 0,10 ;


--推荐使用 
select * from table where id > 243800 order by id limit 10;
idselect_typetabletypepossible_keyskeykey_lenrefrowsExtra
1PRIMARYtb_ams_inf_repay_statrangePRIMARYPRIMARY8NULL3258410Using where
2SUBQUERYtb_ams_inf_repay_statindexNULLidx_orgcd_loannum216NULL19753500Using index

2)Join 连接

-- 0.31 

SELECT *
FROM tb_ams_inf_repay_stat a
       JOIN (select id from tb_ams_inf_repay_stat limit 1000010, 10) b ON a.ID = b.id
idselect_typetabletypepossible_keyskeykey_lenrefrowsExtra
1PRIMARY<derived2>ALLNULLNULLNULLNULL1000020NULL
1PRIMARYaeq_refPRIMARYPRIMARY8b.id1NULL
2DERIVEDtb_ams_inf_repay_statindexNULLidx_orgcd_loannum216NULL19753500Using index

2、其他优化

1)带有条件的,id 连续的查询(between

0.03s 
select * from tb_ams_inf_repay_stat  where id  between 1000000 and 1000010  	 ;

2)带有条件,id 不连续的查询,考虑建立索引

20s 慢死了
select * from tb_ams_inf_repay_stat  	where org_cd = 'xmsd'  	limit 1000000,10 ;
-- org_cd建立索引

select *
from tb_ams_inf_repay_stat
where org_cd = 'xmsd'
  and id > (select id from tb_ams_inf_repay_stat where org_cd = 'xmsd' limit 1000000,1)
limit 0,10 ;

0.2s 可以说相当的快了 
idselect_typetabletypepossible_keyskeykey_lenrefrowsExtra
1PRIMARYNULLNULLNULLNULLNULLNULLNULL~~~~
2SUBQUERYtb_ams_inf_repay_statrefidx_orgcd_loannumidx_orgcd_loannum93const1Using where; Using index

二、千万测试数据生成

1、存储过程

1)表

create table if not exists `user_demo`
(
    `id`          bigint unsigned not null auto_increment comment '主键',
    `name`        varchar(32)     not null default '' comment '姓名',
    `age`         int             not null default 0 comment '年龄',
    `phone`       varchar(32)     not null default '' comment '电话',
    `email`       varchar(64)     not null default '' comment '邮箱',
    `start_time`  datetime                 default null comment '开始时间',
    `end_time`    datetime                 default null comment '结束时间',
    `valid_flag`  int             not null default 1 comment '1有效 0 废弃',
    `create_time` datetime        not null default current_timestamp comment '创建时间',
    `update_time` datetime        not null default current_timestamp on update current_timestamp comment '更新时间',
    primary key (`id`)
) engine = innodb
  default charset = utf8;

2)存储过程

drop procedure if exists test_batch_create;
create procedure test_batch_create(in loop_counts int, in date varchar(50))
begin
    declare i int;
    set i = 0;
    set autocommit = 0; -- 关闭自动提交事务,提高插入效率
    while i < loop_counts
        do
            insert into `user_demo` (name, age, phone, email, start_time, end_time, valid_flag)
            values (concat('张', floor(rand() * 2 * i)), floor(rand() * i), floor(rand() * 3 * i),
                    concat( floor(rand() * 2 * i), '@gmail.com'), date_add(date ,interval i day), date_add(date ,interval i*2 day), 1);
            set i = i + 1;
        end while;
    commit;
end;

3)执行

CALL test_batch_create(10, '2023-07-01');

三、3大数据量查询

1、对比以下三种方式

1)流式查询 (大数据量建议)

a、优势:

1、可以逐步返回结果,无需等待全部结果返回,适用于大数据量查询,可以快速获取部分结果

2、可以在获取部分结果后即时停止查询,提高效率。

3、可以使用游标来记录查询位置,方便下一次继续查询。

b、劣势:

1、数据量较小的情况下,使用流式查询可能会增加复杂度。

2、对于需要精确控制返回结果数量和顺序的情况下,流式查询可能不太适合。

c、使用场景:

1、需要查询大数据量的情况下,可以使用流式查询来快速获取部分结果。

2、对于需要逐步处理数据的情况下,可以使用流式查询来获取部分结果并即时处理。

d、性能:

1、流式查询的性能取决于数据量和查询条件,可以在大数据量的情况下提高效率。

e、打怪

问题一、相比于 IdSize 查询

1、流式查询的优势在于可以逐步返回结果,无需等待全部结果返回。而 IdSize 查询需要等待全部结果返回后再进行排序和筛选,因此在处理大数据量时,IdSize 查询可能会比较耗时。

2、此外,流式查询可以使用游标来记录查询位置,方便下一次继续查询,而 IdSize 查询则需要重新计算起始位置和数量,也会增加查询的时间和复杂度。因此,对于需要处理大数据量的情况,使用流式查询可以提高效率和灵活性。

3、另外,相比于IdSize查询,流式查询可以更灵活地控制返回结果的数量和顺序,可以根据具体需求来获取部分或全部结果。但是需要注意的是,对于需要精确控制返回结果数量和顺序的情况下,使用IdSize查询可能更为适合。

问题二:相比于limit分页查询

1、流式查询可以逐步返回结果,无需等待全部结果返回,可以在获取部分结果后即时停止查询,提高效率。 而 limit分页查询需要多次查询,每次查询返回的结果数量较少,会增加查询的时间和复杂度。

2、流式查询可以使用游标来记录查询位置,方便下一次继续查询。而limit 分页查询需要重新计算偏移量和数量,也会增加查询的时间和复杂度。

问题3:流式查询的过程中,数据库连接会一直保持打开状态,因此可能会影响应用程序的其他数据库操作。

1、使用单独的数据库连接来进行流式查询,

2、流式查询过程中暂停其他的数据库操作。

3、一些数据库操作框架或库会提供一些控制数据库连接生命周期的选项,例如设置连接池大小或超时时间等,可以根据具体情况来进行调整。

4、如果要查询的数据量非常大,可以将数据分成若干批次进行查询,每次查询的数据量较小。这样做有助于减少单次查询的数据量,降低查询的时间和复杂度,提高查询效率和稳定性。

例如,在查询一个包含10000万条记录的数据表时,可以将数据分成100个批次,每个批次查询100万条记录。这样做既可以减少单次查询的数据量,也可以避免长时间占用数据库连接,从而提高查询效率和稳定性。同时,也可以根据具体情况来调整批次大小和查询间隔时间等参数,以达到最优的查询效果。

问题4:和游标查询有啥区别呢?

⬤ 游标查询是一种逐行检索数据的方式,查询结果集会被缓存到数据库内存中(离大谱),然后一次返回一行数据给客户端。这样可以减少客户端内存的消耗,同时也可以避免一次性将整个结果集加载到数据库内存中导致内存溢出。 缺点是速度较慢,因为需要依次检索每一行数据

⬤ 流式查询是一种基于数据流的查询方式,它通过分批次返回数据来避免将整个结果集缓存到内存中。这种查询方式适用于需要对大量数据进行实时处理的场景,如实时监控和实时计算。流式查询的优点是速度较快,因为可以并行处理多个数据流,但缺点是可能会占用大量内存

2)limit 分页查询

a、优势:

1、可以精确控制返回结果的数量和顺序,适用于需要查看部分结果的情况下。

2、可以控制每页返回的数量和偏移量,方便分页显示数据。

3、对于需要精确控制返回结果数量和顺序的情况下,limit 分页查询是一种较为方便的方式。

b、劣势:

1、当数据量非常大时,使用 limit 分页查询可能会影响性能。

2、对于需要获取全部数据或者需要逐步处理数据的情况下,limit 分页查询可能不太适合。

1、当需要获取全部数据时,使用 limit 分页查询需要多次查询,每次查询返回的结果数量较少,会增加查询的时间和复杂度

2、当需要逐步处理数据时,使用 limit 分页查询需要多次查询,每次查询返回的结果数量较少,会增加处理数据的时间和复杂度。

可以考虑使用流式查询来获取全部数据或者逐步处理数据。流式查询可以逐步返回结果,无需等待全部结果返回,适用于大数据量查询,可以快速获取部分结果。同时可以在获取部分结果后即时停止查询,提高效率。虽然limit分页查询也是逐步返回结果,但是与流式查询相比,在处理大数据量时可能会更加耗时。如果需要精确控制返回结果数量和顺序,可以使用Idsize查询来获取一定数量的结果

c、使用场景:

1、需要查看部分结果或者进行分页显示数据的情况下,可以使用 limit 分页查询。

2、对于需要精确控制返回结果数量和顺序的情况下,limit 分页查询是一种较为方便的方式。

d、性能:

1、limit 分页查询的性能取决于数据量和查询条件,当数据量非常大时可能会影响性能。

3)Idsize 查询

a、优势:

1、可以根据 id 的大小关系进行查询,可以精确控制返回结果的数量和顺序。

2、性能较好,特别是在有索引的情况下。

b、劣势:

1、对于需要进行复杂筛选或者关联查询的情况下,Idsize 查询可能不太适合。

2、对于没有 id 或者 id 不规则的数据表,Idsize 查询无法使用。

c、使用场景:

1、需要根据 id 进行排序或者获取一定数量的结果的情况下,可以使用 Idsize查询。

2、当有索引字段并且需要精确控制返回结果数量和顺序时,Idsize 查询是一种较为方便的方式。

d、性能:

Idsize 查询的性能取决于数据量和索引情况,当有索引字段并且需要根据 id 进行排序或者获取一定数量的结果时,性能较好。

2、线程池 Id 分段查询

1)线程池 Id 分段工具

package com.healerjean.proj.utils.db;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletionService;
import java.util.concurrent.Future;
import java.util.function.BiFunction;
import java.util.function.Function;

/**
 * MybatisPlusQueryUtils
 *
 * @author zhangyujin
 * @date 2023/6/25$  12:05$
 */
public final class MybatisBatchUtils {



    /**
     * queryAll
     *
     * @param executorService 线程池
     * @param function        分页函数
     * @param query           查询条件
     * @param minMax          minMax 最小Id和最大Id
     * @param coverFunction   coverFunction 对象转化
     */
    public static <Q, R, T> List<Future<List<T>>> queryAllByPoolIdSub(CompletionService<List<T>> executorService,
                                                                      BiFunction<IdQueryBO, Q, List<R>> function,
                                                                      Q query,
                                                                      IdQueryBO minMax,
                                                                      Function<List<R>, List<T>> coverFunction) {
        Long minId = minMax.getMinId();
        Long maxId = minMax.getMaxId();
        Long size = minMax.getSize();
        List<Future<List<T>>> result = new ArrayList<>();
        for (long i = minId; i <= maxId; i = i + size) {
            long endId = Math.min(i + size, maxId);
            boolean maxEqualFlag = endId == maxId;
            IdQueryBO idQueryBO = new IdQueryBO(true, i, maxEqualFlag, endId, size);
            Future<List<T>> future = executorService.submit(() -> {
                List<R> list = function.apply(idQueryBO, query);
                return coverFunction.apply(list);
            });
            result.add(future);
            if (maxEqualFlag) {
                break;
            }
        }
        return result;
    }

}

2)Manager

/**
 * 根据查询条件获取最大Id和最小Id
 *
 * @param query query
 * @return ImmutablePair<Long, Long>
 */
@Override
public ImmutablePair<Long, Long> queryMinAndMaxId(UserDemoQueryBO query) {
    QueryWrapper<UserDemo> queryWrapper = new QueryWrapper<>();
    queryWrapper.select("min(id) as \"minId\"", "max(id) as \"maxId\"");
    LambdaQueryWrapper<UserDemo> lambdaQueryWrapper = queryWrapper.lambda()
            .eq(Objects.nonNull(query.getId()), UserDemo::getId, query.getId())
            .eq(Objects.nonNull(query.getValidFlag()), UserDemo::getValidFlag, query.getValidFlag())
            .like(StringUtils.isNotBlank(query.getName()), UserDemo::getName, query.getName())
            .like(StringUtils.isNotBlank(query.getPhone()), UserDemo::getPhone, query.getPhone())
            .like(StringUtils.isNotBlank(query.getEmail()), UserDemo::getEmail, query.getEmail())
            .like(StringUtils.isNotBlank(query.getLikeName()), UserDemo::getName, query.getLikeName())
            .like(StringUtils.isNotBlank(query.getLikePhone()), UserDemo::getPhone, query.getLikePhone())
            .le(Objects.nonNull(query.getQueryTime()), UserDemo::getStartTime, query.getQueryTime())
            .ge(Objects.nonNull(query.getQueryTime()), UserDemo::getEndTime, query.getQueryTime());
    Map<String, Object> map = userDemoDao.getMap(lambdaQueryWrapper);
    return ImmutablePair.of(MapUtils.getLong(map, "minId"), MapUtils.getLong(map, "maxId"));
}

/**
 * 根据id区间查询数据
 *
 * @param idQueryBO idQueryBO
 * @return {@link List< UserDemo>}
 */
@Override
public List<UserDemo> queryUserDemoByIdSub(IdQueryBO idQueryBO, UserDemoQueryBO query) {
    QueryWrapper<UserDemo> queryWrapper = new QueryWrapper<>();
    if (!CollectionUtils.isEmpty(query.getSelectFields())) {
        queryWrapper.select(StringUtils.join(query.getSelectFields(), ","));
    }
    LambdaQueryWrapper<UserDemo> lambdaQueryWrapper = queryWrapper.lambda()
            .eq(Objects.nonNull(query.getId()), UserDemo::getId, query.getId())
            .eq(Objects.nonNull(query.getValidFlag()), UserDemo::getValidFlag, query.getValidFlag())
            .eq(StringUtils.isNotBlank(query.getName()), UserDemo::getName, query.getName())
            .eq(StringUtils.isNotBlank(query.getPhone()), UserDemo::getPhone, query.getPhone())
            .eq(StringUtils.isNotBlank(query.getEmail()), UserDemo::getEmail, query.getEmail())
            .like(StringUtils.isNotBlank(query.getLikeName()), UserDemo::getName, query.getLikeName())
            .like(StringUtils.isNotBlank(query.getLikePhone()), UserDemo::getPhone, query.getLikePhone())
            .lt(Objects.nonNull(query.getQueryTime()), UserDemo::getStartTime, query.getQueryTime())
            .gt(Objects.nonNull(query.getQueryTime()), UserDemo::getEndTime, query.getQueryTime());

    if (idQueryBO.getMinEqualFlag()) {
        lambdaQueryWrapper.ge(UserDemo::getId, idQueryBO.getMinId());
    } else {
        lambdaQueryWrapper.gt(UserDemo::getId, idQueryBO.getMinId());
    }

    if (idQueryBO.getMaxEqualFlag()) {
        lambdaQueryWrapper.le(UserDemo::getId, idQueryBO.getMaxId());
    } else {
        lambdaQueryWrapper.lt(UserDemo::getId, idQueryBO.getMaxId());
    }
    return userDemoDao.list(lambdaQueryWrapper);
}

3)BO 对象

a、IdQueryBO

package com.healerjean.proj.utils.db;

import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
 * @author zhangyujin
 * @date 2023/7/5$  15:45$
 */
@Accessors(chain = true)
@Data
public class IdQueryBO implements Serializable {

    /**
     * serialVersionUID
     */
    private static final long serialVersionUID = 3145985803712258405L;

    /**
     * 是否等于最小
     */
    private Boolean minEqualFlag;
    /**
     * 最小Id
     */
    private Long minId;

    /**
     * 是否等于最大
     */
    private Boolean maxEqualFlag;
    /**
     * 最大Id
     */
    private Long maxId;

    /**
     * 每次查询多少个
     */
    private Long size;

    public IdQueryBO(Long minId, Long maxId, Long size) {
        this.minId = minId;
        this.maxId = maxId;
        this.size = size;
    }

    public IdQueryBO(Boolean minEqualFlag, Long minId, Boolean maxEqualFlag, Long maxId, Long size) {
        this.minEqualFlag = minEqualFlag;
        this.minId = minId;
        this.maxEqualFlag = maxEqualFlag;
        this.maxId = maxId;
        this.size = size;
    }
}

4)Service

    /**
     * 根据
     *
     * @param completionService completionService
     * @param query             queryBO
     * @return List<Future < List < UserDemoExcel>>>
     */
    @Override
    public List<Future<List<UserDemoExcel>>> queryAllUserDemoByPoolIdSub(CompletionService<List<UserDemoExcel>> completionService, UserDemoQueryBO query) {
        ImmutablePair<Long, Long> minAndMaxId = userDemoManager.queryMinAndMaxId(query);
        Long minId = minAndMaxId.getLeft();
        Long maxId = minAndMaxId.getRight();
        IdQueryBO idQueryBO = new IdQueryBO(minId, maxId, 2L);
        return MybatisBatchUtils.queryAllByPoolIdSub(completionService,
                (p, q) -> userDemoManager.queryUserDemoByIdSub(p, q),
                query,
                idQueryBO,
                UserConverter.INSTANCE::covertUserDemoPoToExcelList);
    }

5)单测

@DisplayName("testQueryMinAndMaxId")
@Test
public void testQueryMinAndMaxId() {
    CompletionService<List<UserDemoExcel>> completionService = new ExecutorCompletionService(ThreadPoolUtils.DEFAULT_THREAD_POOL_TASK_EXECUTOR);

    UserDemoQueryBO queryBO = new UserDemoQueryBO();
    List<Future<List<UserDemoExcel>>> futures = userDemoService.queryAllUserDemoByPoolIdSub(completionService, 
                                                                                            queryBO);
    List<UserDemoExcel> all = Lists.newArrayList();
    for (int i = 0; i < futures.size(); i++) {
        try {
            Future<List<UserDemoExcel>> future = completionService.take();
            List<UserDemoExcel> userDemos = future.get();
            if (CollectionUtils.isEmpty(userDemos)) {
                continue;
            }
            all.addAll(future.get());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    log.info("[testQueryMinAndMaxId] res:{}", JsonUtils.toString(all));
}

3、线程池 limit 分页查询

1)线程池分页读工具

package com.healerjean.proj.utils.db;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletionService;
import java.util.concurrent.Future;
import java.util.function.BiFunction;
import java.util.function.Function;

/**
 * MybatisPlusQueryUtils
 *
 * @author zhangyujin
 * @date 2023/6/25$  12:05$
 */
public final class MybatisBatchUtils {

    /**
     * queryAll
     *
     * @param executorService 线程池
     * @param function        分页函数
     * @param queryWrapper    查询条件
     * @param pageSize        pageSize 分页大小
     * @param coverFunction   coverFunction 对象转化
     * @return {@link List< Future< List<T>>>}
     */
    public static <Q, R, T> List<Future<List<T>>> queryAllByPoolLimit(CompletionService<List<T>> executorService,
                                                                      BiFunction<Page<R>, Q, IPage<R>> function,
                                                                      Q queryWrapper,
                                                                      int pageSize,
                                                                      Function<List<R>, List<T>> coverFunction) {

        IPage<R> initPage = function.apply(new Page<>(1, 0, true), queryWrapper).setSize(pageSize);

        List<Future<List<T>>> result = new ArrayList<>();
        for (int i = 1; i <= initPage.getPages(); i++) {
            int finalI = i;
            Future<List<T>> future = executorService.submit(() -> {
                IPage<R> page = function.apply(new Page<>(finalI, pageSize, false), queryWrapper);
                return coverFunction.apply(page.getRecords());
            });
            result.add(future);
        }
        return result;
    }


}

2)Manager

/**
 * 获取查询条件
 *
 * @param query query
 * @return LambdaQueryWrapper<UserDemo>
 */
@Override
public LambdaQueryWrapper<UserDemo> queryBuilderQueryWrapper(UserDemoQueryBO query) {
    QueryWrapper<UserDemo> queryWrapper = new QueryWrapper<>();
    if (!CollectionUtils.isEmpty(query.getSelectFields())) {
        queryWrapper.select(StringUtils.join(query.getSelectFields(), ","));
    }
    if (!CollectionUtils.isEmpty(query.getOrderByList())) {
        query.getOrderByList().forEach(item -> queryWrapper.orderBy(Boolean.TRUE, item.getDirection(), 
                                                                    item.getProperty()));
    }
    return queryWrapper.lambda()
            .eq(Objects.nonNull(query.getId()), UserDemo::getId, query.getId())
            .eq(Objects.nonNull(query.getValidFlag()), UserDemo::getValidFlag, query.getValidFlag())
            .eq(StringUtils.isNotBlank(query.getName()), UserDemo::getName, query.getName())
            .eq(StringUtils.isNotBlank(query.getPhone()), UserDemo::getPhone, query.getPhone())
            .eq(StringUtils.isNotBlank(query.getEmail()), UserDemo::getEmail, query.getEmail())
            .like(StringUtils.isNotBlank(query.getLikeName()), UserDemo::getName, query.getLikeName())
            .like(StringUtils.isNotBlank(query.getLikePhone()), UserDemo::getPhone, query.getLikePhone())
            .lt(Objects.nonNull(query.getQueryTime()), UserDemo::getStartTime, query.getQueryTime())
            .gt(Objects.nonNull(query.getQueryTime()), UserDemo::getEndTime, query.getQueryTime());
}

3)Service

/**
 * queryFutureAll
 *
 * @param completionService completionService
 * @param query             query
 * @return List<Future < List < UserDemoExcel>>>
 */
@Override
public List<Future<List<UserDemoExcel>>> queryAllUserDemoByPoolLimit(CompletionService<List<UserDemoExcel>> completionService, UserDemoQueryBO query) {
    QueryWrapper<UserDemo> queryWrapper = userDemoManager.queryBuilderQueryWrapper(query);
    return MybatisBatchUtils.queryAllByPoolLimit(completionService,
            (p, q) -> userDemoDao.page(p, q),
            queryWrapper,
            1,
            UserConverter.INSTANCE::covertUserDemoPoToExcelList);
}

4)单测

@DisplayName("testQueryUserDemoByLimit")
@Test
public void testQueryUserDemoByLimit() {
    CompletionService<List<UserDemoExcel>> completionService = new ExecutorCompletionService(ThreadPoolUtils.DEFAULT_THREAD_POOL_TASK_EXECUTOR);

    UserDemoQueryBO queryBO = new UserDemoQueryBO();
    List<Future<List<UserDemoExcel>>> futures = userDemoService.queryAllUserDemoByPoolLimit(completionService, 
                                                                                            queryBO);
    List<UserDemoExcel> all = Lists.newArrayList();
    for (int i = 0; i < futures.size(); i++) {
        try {
            Future<List<UserDemoExcel>> future = completionService.take();
            List<UserDemoExcel> userDemos = future.get();
            if (CollectionUtils.isEmpty(userDemos)) {
                continue;
            }
            all.addAll(future.get());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    log.info("[testQueryUserDemoByLimit] res:{}", JsonUtils.toString(all));
}

四、工具类

package com.healerjean.proj.utils.db;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletionService;
import java.util.concurrent.Future;
import java.util.function.BiFunction;
import java.util.function.Function;

/**
 * MybatisPlusQueryUtils
 *
 * @author zhangyujin
 * @date 2023/6/25$  12:05$
 */
public final class MybatisBatchUtils {

    /**
     * queryAll
     *
     * @param function     function
     * @param queryWrapper queryWrapper
     * @param pageSize     pageSize
     * @return {@link List<R>}
     */

    public static <Q, R> List<R> queryAllByLimit(BiFunction<Page<R>, Q, IPage<R>> function,
                                                 Q queryWrapper,
                                                 long pageSize) {

        IPage<R> initPage = function.apply(new Page<>(1, 0, true), queryWrapper).setSize(pageSize);
        List<R> result = new ArrayList<>();
        for (int i = 1; i <= initPage.getPages(); i++) {
            IPage<R> page = function.apply(new Page<>(i, pageSize, false), queryWrapper);
            if (CollectionUtils.isEmpty(page.getRecords())) {
                break;
            }
            result.addAll(page.getRecords());
        }
        return result;
    }


    /**
     * queryAll
     *
     * @param function function
     * @param query    query
     * @param minMax   minMax
     * @return {@link List<R>}
     */
    public static <Q, R> List<R> queryAllByIdSize(BiFunction<IdQueryBO, Q, List<R>> function,
                                                  Q query,
                                                  IdQueryBO minMax) {
        List<R> result = Lists.newArrayList();
        Long minId = minMax.getMinId();
        Long size = minMax.getSize();
        while (minId != null) {
            IdQueryBO idQueryBO = new IdQueryBO(minId, size);
            List<R> dbList = function.apply(idQueryBO, query);
            if (CollectionUtils.isEmpty(dbList)) {
                break;
            }
            minId = idQueryBO.getMaxId();
            result.addAll(dbList);
        }
        return result;
    }

    /**
     * queryAll
     *
     * @param function function
     * @param query    query
     * @param minMax   minMax
     * @return {@link List<R>}
     */
    public static <Q, R> List<R> queryAllByIdSub(BiFunction<IdQueryBO, Q, List<R>> function,
                                                 Q query,
                                                 IdQueryBO minMax) {
        List<R> result = Lists.newArrayList();
        Long minId = minMax.getMinId();
        Long maxId = minMax.getMaxId();
        Long size = minMax.getSize();
        for (long i = minId; i <= maxId; i = i + size) {
            long endId = Math.min(i + size, maxId);
            boolean maxEqualFlag = endId == maxId;
            IdQueryBO idQueryBO = new IdQueryBO(true, i, maxEqualFlag, endId, size);
            List<R> dbList = function.apply(idQueryBO, query);
            if (CollectionUtils.isEmpty(dbList)) {
                break;
            }
            result.addAll(dbList);
            if (maxEqualFlag) {
                break;
            }
        }
        return result;
    }


    /**
     * queryAll
     *
     * @param executorService 线程池
     * @param function        分页函数
     * @param queryWrapper    查询条件
     * @param pageSize        pageSize 分页大小
     * @param coverFunction   coverFunction 对象转化
     * @return {@link List< Future< List<T>>>}
     */
    public static <Q, R, T> List<Future<List<T>>> queryAllByPoolLimit(CompletionService<List<T>> executorService,
                                                                      BiFunction<Page<R>, Q, IPage<R>> function,
                                                                      Q queryWrapper,
                                                                      int pageSize,
                                                                      Function<List<R>, List<T>> coverFunction) {

        IPage<R> initPage = function.apply(new Page<>(1, 0, true), queryWrapper).setSize(pageSize);

        List<Future<List<T>>> result = new ArrayList<>();
        for (int i = 1; i <= initPage.getPages(); i++) {
            int finalI = i;
            Future<List<T>> future = executorService.submit(() -> {
                IPage<R> page = function.apply(new Page<>(finalI, pageSize, false), queryWrapper);
                return coverFunction.apply(page.getRecords());
            });
            result.add(future);
        }
        return result;
    }


    /**
     * queryAll
     *
     * @param executorService 线程池
     * @param function        分页函数
     * @param query           查询条件
     * @param minMax          minMax 最小Id和最大Id
     * @param coverFunction   coverFunction 对象转化
     */
    public static <Q, R, T> List<Future<List<T>>> queryAllByPoolIdSub(CompletionService<List<T>> executorService,
                                                                      BiFunction<IdQueryBO, Q, List<R>> function,
                                                                      Q query,
                                                                      IdQueryBO minMax,
                                                                      Function<List<R>, List<T>> coverFunction) {
        Long minId = minMax.getMinId();
        Long maxId = minMax.getMaxId();
        Long size = minMax.getSize();
        List<Future<List<T>>> result = new ArrayList<>();
        for (long i = minId; i <= maxId; i = i + size) {
            long endId = Math.min(i + size, maxId);
            boolean maxEqualFlag = endId == maxId;
            IdQueryBO idQueryBO = new IdQueryBO(true, i, maxEqualFlag, endId, size);
            Future<List<T>> future = executorService.submit(() -> {
                List<R> list = function.apply(idQueryBO, query);
                return coverFunction.apply(list);
            });
            result.add(future);
            if (maxEqualFlag) {
                break;
            }
        }
        return result;
    }

}

ContactAuthor