简介
why: 最近在项目中,有一个sql需要查询100多万的数据,且需要在代码中遍历处理。面临两个问题
- 一次性查询出太多数据,速度较慢
- 当前服务器内存支持以上操作,但是随着数据量的增多,以后可能会出现内存溢出的问题
需要采用 批量查询和批量消费的方式,但是我并不想直接写到我的业务代码中,我想直接写一个公共方法。
who: 想要批量查询和消费的公共方法,该公共方法以PageHelper分页插件实现分页查询,用多线程来批量消费
what:更快速的查询出这些数据,同时性能上尽量好一点,并且不会造成内存溢出。可以采用 分页查询或者分批查询,并且每次查询出来后,直接调用消费函数(入参)消费
详细要求:
-
写一个公共方法来实现我的目的
-
这个公共方法用泛型来实现,这样可以支持不同mapper中的不同查询方法
-
公共方法的入参 回参 名称:
入参:
- 对应的mapper类:Class< T > mapperClass,
- 查询方法:查询方法必须是mapperClass上的方法
- Object… params(查询方法的入参),
- 消费函数(消费函数是由入参对象的)
回参:void
代码
整体涉及到的
- mapper查询方法
- 批量查询and消费的方法
- 调用处
1. mapper查询方法
@Mapper
public interface UserMapper {
List<UserEntity> queryUerList(@Param("name") String name, @Param("age") String age, @Param("validation") String validation);
int queryUserTotal(@Param("name") String name, @Param("age") String age, @Param("validation") String validation);
}
在这里插入图片描述
2. BatchPageQueryUtil.queryWithTotalFunction 批量查询 and消费的公共方法
类
package com.svllen.studyspringmvc.util;
import com.github.pagehelper.PageHelper;
import jakarta.annotation.Resource;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.ToIntBiFunction;
/**
* @description: 批量查询和消费的函数
* @author: svllen
* @create: 2024-12-09 14:18
**/
@Component
public class BatchPageQueryUtil {
@Resource
private SqlSessionFactory sqlSessionFactory;
@Resource
private ThreadPoolTaskExecutor batchQueryExecutor;
/**
* @Description 第二版方法
* @param mapperClass mapper累
* @param queryFunction 查询数据函数
* @param queryTotalFunction 查询总数的函数
* @param consumer 消费函数
* @param pageSize 分页参数-每一页多少数量
* @param params 查询方法的参数
* @param <T> mapper类的类型
* @param <R> 查询出来的数据对象类型
*
* @Author svllen
* @date 2024-12-09 14:48
*/
public <T, R> void queryWithTotalFunction(Class<T> mapperClass, BiFunction<T, Object[], List<R>> queryFunction,
ToIntBiFunction<T, Object[]> queryTotalFunction,
Consumer<List<R>> consumer,
int pageSize, Object... params) {
int totalRecords = getTotalCount(mapperClass, queryTotalFunction, params);
int totalPages = (int) Math.ceil((double) totalRecords / pageSize);
// 使用 CompletableFuture 异步执行任务
CompletableFuture[] futures = new CompletableFuture[totalPages];
for (int i = 0; i < totalPages; i++) {
int pageNum = i + 1; // 页码从 1 开始
futures[i] = CompletableFuture.runAsync(() -> {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
T mapper = sqlSession.getMapper(mapperClass);
// 设置分页参数
PageHelper.startPage(pageNum, pageSize, false);
// 执行查询
List<R> results = queryFunction.apply(mapper, params);
if (results != null && !results.isEmpty()) {
// 调用消费函数
consumer.accept(results);
}
}
}, batchQueryExecutor);
}
// 等待所有任务完成
CompletableFuture.allOf(futures).join();
}
private <T> int getTotalCount(Class<T> mapperClass, ToIntBiFunction<T, Object[]> queryTotalFunction, Object... params) {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
T mapper = sqlSession.getMapper(mapperClass);
return queryTotalFunction.applyAsInt(mapper, params);
} catch (Exception e) {
throw new RuntimeException("获取总记录数失败", e);
}
}
}
3. 调用
这次是用了一个接口去调用,实际中一般是放在service层
@RestController
@RequestMapping("/test")
public class FirstController {
@GetMapping("/batchQueryAndConsumer")
public void batchQueryAndConsumer() {
System.out.println("进来了=========== " + true);
QueryFirstVO queryFirstVO = new QueryFirstVO("张三", 25.6);
Consumer<List<UserEntity>> consumer = (list) ->{
consumerData(list, queryFirstVO);
};
batchPageQueryUtil.queryWithTotalFunction(UserMapper.class,
(mapper, param) -> mapper.queryUerList((String)param[0], (String)param[1], (String)param[2]),
(mapper, param) -> mapper.queryUserTotal((String)param[0], (String)param[1], (String)param[2]),
consumer, 1000, "张三", "25.6", "1");
}
private void consumerData(List<UserEntity> list, QueryFirstVO queryFirstVO) {
System.out.println("开始消费数据 ");
list.forEach(userEntity -> {
System.out.println(userEntity.getName());
System.out.println(userEntity.getAge());
});
}
}
方法说明:
这里创建了一个测试的接口,调用处
- Consumer<List> consumer = () -> {} : 自定义的消费函数,这里写在查询方法前,并不意味着先执行消费语句
- batchPageQueryUtil.queryWithTotalFunction:
- UserMapper.class:查询方法内部是根据类型获取对应的mapper,如果有多个类型相似的,查询方法内部可以改为By Name获取Bean
- (mapper, param) -> mapper.queryUerList((String)param[0], (String)param[1], (String)param[2]):查询数据的sql
- (mapper, param) -> mapper.queryUserTotal((String)param[0], (String)param[1], (String)param[2]):查询总数据的sql