从数据库多线程导出数据到Excel(千万级数据导出),附代码

从数据库多线程导出数据到Excel

在业务中,经常会碰到从数据库导出数据到Excel中的场景,这里总结了使用EasyExcel导出千万级数据的过程。最终实现 千万级数据导出到 一张excel中多个sheet页!

    要知道以 .xlsx结尾的excel文件每个sheet 只能写104万左右的数据量,
    如果想要写入500W条数据到excel,要么分到多个sheet中,每个sheet存100w左右数据,5个sheet存储完;
    要么写到五个xlsx文件中,这可能不是想要的。所以写入到同一个表格文件不同的sheet中去。
    封装的easyexcel只用于写数据,只要内存和硬盘足够大,亿万条数据也不在话下,但是无实际意义吧。

直接上代码:

1. 引入easyexcel依赖

<!--引入easyexcel依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.2.3</version>
        </dependency>

2. 封装easyexcel工具类

主要用到EasyExcel、ExcelWriter、WriterSheet等主要类,想要深入研究的同学可以自行看源码。

public class ExcelHandler {

    private List<WriteSheet> sheets;
    private String dirName;
    private static final Integer DEFAULT_SHEETS_NUM = 5;
    private static final Integer DEFAULT_PER_SHEET_NUM = 1000000;

    public ExcelHandler(){}

    public ExcelHandler(String folderName){
        dirName = folderName;
    }

    /**
     *  创建excel和sheet,创建时可以指定sheet数量
     * @param excelName
     * @param clazz
     * @param sumSheet
     * @return
     */
    public ExcelWriter create(String excelName, Class clazz, int sumSheet){
        ExcelWriter excelWriter = EasyExcel.write(route(excelName), clazz.asSubclass(clazz)).build();
        createSheets(sumSheet);
        return excelWriter;
    }

    /**
     *  创建excel和sheet,sheet 数量默认 5, 最高可存放500W 行左右的数据,受每个sheet存放数据的限制
     * @param excelName
     * @param clazz
     * @return
     */
    public ExcelWriter create(String excelName, Class clazz){
        ExcelWriter excelWriter = EasyExcel.write(route(excelName), clazz.asSubclass(clazz)).build();
        createSheets(DEFAULT_SHEETS_NUM);
        return excelWriter;
    }

    /**
     *  写数据到excel, 仅使用一个sheet,不可用于百万以上数据
     * @param excelWriter
     * @param list
     */
    public void write(ExcelWriter excelWriter, List list){
        excelWriter.write(list, sheets.get(0));
    }

    /**
     *  写数据到excel
     * @param excelWriter
     * @param list    每一次的数据
     * @param sheetNum  sheet页码
     */
    public void write(ExcelWriter excelWriter, List list, int sheetNum){
        excelWriter.write(list, sheets.get(sheetNum));
    }

    /**
     *  写完数据关闭(finish 有关流操作),必须的操作
     * @param excelWriter
     */
    public void finish(ExcelWriter excelWriter){
        excelWriter.finish();
    }

    /**
     *  创建指定数量的sheet
     * @param num
     */
    private void createSheets(int num){
        sheets = new ArrayList();
        for (int i = 0; i <= num; i++) {
            WriteSheet sheet = EasyExcel.writerSheet(i, "sheet"+i).build();
            sheets.add(sheet);
        }
    }

    /**
     *  获取excel存放路径
     * @param excelName
     * @return
     */
    private String route(String excelName){
        if (null == dirName){
            dirName = "D:\\data\\excel";
        }
        String filePath = "D:\\data\\excel\\" + dirName + "/";
        File file = new File(filePath);
        if (!file.exists()){
            file.mkdirs();
        }
        return filePath + excelName + ".xlsx";
    }
}

3. 数据实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
@ContentRowHeight(15)
@HeadRowHeight(20)
@ColumnWidth(25)
public class Data1 {

    @ExcelProperty("账号")
    private String account;
    @ExcelProperty("密码")
    private String password;
}

4. 如何使用工具类

public class Test {
    public static void main(String[] args) {
        Test tt = new Test();
        tt.test01();
    }

    /**
     *  模拟百万以下数据导入到excel
     */
    private void test00(){
        int num = 0;
        ExcelHandler handler = null;
        ExcelWriter writer = null;
        try {
            // 创建handler对象 -- 参数文件夹名
            handler = new ExcelHandler("test_00");
            writer = handler.create("记录", Data1.class);

            List list = new ArrayList(1024);
            // 方式一:一次性导出
            for (int i = 0; i < 1000000; i++) {
                num++;
                list.add(new Data1("张三"+num, "123abc"+num));
            }
            handler.write(writer, list);


            // 方式二:分多次导出,为了降低导出过程中的内存资源,可以分多次导出
            for (int a = 0; a < 100; a++) { // 模拟拿100次数据,每次拿10000条数据
                for (int i = 0; i < 10000; i++) { // 模拟一次拿10000条数据
                    num++;
                    list.add(new Data1("张三"+num, "123abc"+num));
                }
                handler.write(writer, list);
                // 防止数据重复,及时情况list集合
                list.clear();
            }


        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (null != writer){
                handler.finish(writer);
            }
        }
    }

    /**
     *  模拟百万以上数据导入到excel
     */
    private void test01(){
        int num = 0;
        ExcelHandler handler = null;
        ExcelWriter writer = null;
        try {
            // 创建handler对象 -- 参数文件夹名
            handler = new ExcelHandler("test_01");
            writer = handler.create("记录", Data1.class, 10);

            List list = new ArrayList(1024);
            // 此处依旧可以模仿test00去优化
            // TODO 考虑能否做成异步方式,用阻塞队列,生产和消费同事进行,一边从数据库查询数据,一边导入数据到excel

            for (int a = 0; a < 10; a++) { // 模拟分页页数, 每页50W数据
                for (int i = 0; i < 500000; i++) { // 模拟每页数据量
                    num++;
                    list.add(new Data1("张三"+num, "123abc"+num));
                }
                handler.write(writer, list, a);
                // 防止数据重复,及时情况list集合
                list.clear();
            }

            // 考虑从数据库查询数据这种情况
            // 先查询数据总量,然后计算分页数,按照每页50W
            int allCount = 7500000; // 数据总条数 750W
            int pageSize = (int) Math.ceil(allCount/1000000.0);
            writer = handler.create("MySQL数据", Data1.class, pageSize);
            // TODO 效率问题,可以考虑多线程
            // TODO 每个线程处理部分数据,处理 步进的数据量,然后再可以根据下面的分页查询数据
            // TODO 每个处理线程CountDownLaunch处理,主线程 ctl.await()阻塞,所有处理线程完成之后,再进行下面的业务
            for (int k = 0; k < pageSize; k++) {
                // 从数据库分页查询数据
                List<Data1> datasByMySQL = selectByMySQL(new HashMap<>(), k*500000, 500000);
                handler.write(writer, datasByMySQL, k);
                datasByMySQL.clear();
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (null != writer){
                handler.finish(writer);
            }
        }
    }

    /**
     *  从数据库查询数据
     * @param params  条件查询的params
     * @param offset  偏移量
     * @param rows    行数
     * @return
     */
    private List<Data1> selectByMySQL(HashMap<Object, Object> params, int offset, int rows) {
        // TODO 从数据库查询数据  select * from table where param=#{param} limit offset,rows
        return new ArrayList<>();
    }
}
  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值