百万数据导出excel工具类

108 阅读3分钟

一、导入相关依赖

<!--excel导出-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.3.2</version>
</dependency>

二、BaseEasyExcelExport工具类

BaseEasyExcelExport类为抽象父类位于ape-common-tool模块下,业务模块使用需要继承父类,并重写抽象方法实现定制化。

package com.ssm.tool.excel;

import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.WriteTable;
import com.alibaba.excel.write.metadata.WriteWorkbook;
import com.ssm.tool.SpringContextUtil;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.util.*;

/**
 * easyExcel数据导出模板
 */
@Slf4j
public abstract class BaseEasyExcelExport<T> {

    /**
     * 导出到excel,支持大数量的分批写入
     * @param fileName 文件名称
     * @param queryCondition 查询条件
     */
    protected void exportExcel(String fileName, Map<String, Object> queryCondition) {
        HttpServletResponse response = SpringContextUtil.getHttpServletResponse();
        //根据条件查询总记录
        Long totalCount = dataTotalCount(queryCondition);
        //每一个Sheet存放的记录数
        Long sheetDataRows = eachSheetTotalCount();
        //每次写入的数据量
        Long writeDataRows = eachTimesWriteSheetTotalCount();
        if (totalCount < sheetDataRows) {
            sheetDataRows = totalCount; //总记录小于sheet页容量,就把当前sheet页容量当做做那个记录数即可
        }
        if (sheetDataRows < writeDataRows) { //如果sheet页容量小于每次写的数量,那么每次编写的数量就为sheet页容量
            writeDataRows = sheetDataRows;
        }
        doExport(response, fileName, queryCondition, totalCount, sheetDataRows, writeDataRows);
    }

    /**
     * 导出到excel
     * @param response
     * @param fileName 文件名
     * @param queryCondition 查询条件
     * @param totalCount 总记录数
     * @param sheetDataRows sheet页容量
     * @param writeDataRows 每次编写的数量
     */
    private void doExport(HttpServletResponse response, String fileName, Map<String, Object> queryCondition,
                          Long totalCount, Long sheetDataRows, Long writeDataRows) {
        OutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();
            WriteWorkbook writeWorkbook = new WriteWorkbook();
            writeWorkbook.setOutputStream(outputStream);
            writeWorkbook.setExcelType(ExcelTypeEnum.XLSX);
            ExcelWriter writer = new ExcelWriter(writeWorkbook);
            WriteTable table = new WriteTable();
            table.setHead(getExcelHead());
            // 计算需要的Sheet数量
            Long sheetNum = totalCount % sheetDataRows == 0 ? (totalCount / sheetDataRows) : (totalCount / sheetDataRows + 1);
            // 计算一般情况下每一个Sheet需要写入的次数
            Long oneSheetWriteCount = totalCount > sheetDataRows ? sheetDataRows / writeDataRows :
                    totalCount % writeDataRows > 0 ? totalCount / writeDataRows + 1 : totalCount / writeDataRows;
            // 计算最后一个sheet需要写入的次数
            Long lastSheetWriteCount = totalCount % sheetDataRows == 0 ? oneSheetWriteCount :
                    (totalCount % sheetDataRows % writeDataRows == 0 ? (totalCount / sheetDataRows / writeDataRows) :
                            (totalCount / sheetDataRows / writeDataRows + 1));
            // 分批查询分次写入
            List<List<String>> dataList = new ArrayList<>();
            for (int i = 0; i < sheetNum; i++) {
                WriteSheet sheet = new WriteSheet();
                sheet.setSheetNo(i);
                sheet.setSheetName(sheetNum == 1 ? fileName : fileName + i);
                for (int j = 0; j < (i != sheetNum - 1 || i == 0 ? oneSheetWriteCount : lastSheetWriteCount); j++) {
                    dataList.clear();
                    buildDataList(dataList, queryCondition, j + 1 + oneSheetWriteCount * i, writeDataRows);
                    writer.write(dataList, sheet, table);
                }
            }
            response.setHeader("Content-Disposition", "attachment;filename="
                    + new String((fileName).getBytes("gb2312"),
                    "ISO-8859-1") + ".xlsx");
            response.setContentType("application/vnd.ms-excel");
            response.setCharacterEncoding("utf-8");
            writer.finish();
            outputStream.flush();
        } catch (Exception e) {
            log.error("AbstractEasyExcelExport.exportWithBigData.error:{}", e.getMessage(), e);
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (Exception e) {
                    log.error("AbstractEasyExcelExport.exportWithBigData.close.error:{}", e.getMessage(), e);
                }
            }
        }
    }

    /**
     * 获取导出的表头
     */
    protected abstract List<List<String>> getExcelHead();

    /**
     * 计算导出数据的总数
     */
    protected abstract Long dataTotalCount(Map<String, Object> conditions);

    /**
     * 每一个sheet存放的数据总数
     */
    protected abstract Long eachSheetTotalCount();

    /**
     * 每次写入sheet的总数
     */
    protected abstract Long eachTimesWriteSheetTotalCount();

    /**
     * 构建每次写入的数据
     */
    protected abstract void buildDataList(List<List<String>> resultList, Map<String, Object> queryCondition,
                                          Long pageNo, Long pageSize);

}

三、业务模块继承工具类

package com.ssm.user.excel;

import com.ssm.entity.PageResult;
import com.ssm.tool.excel.BaseEasyExcelExport;
import com.ssm.user.convert.UserConvert;
import com.ssm.user.entity.po.UserPo;
import com.ssm.user.entity.req.UserPageReq;
import com.ssm.user.service.UserService;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;

@Component
public class UserExcelExport extends BaseEasyExcelExport<UserPo> {

    @Resource
    private UserService userService;

    /**
     * 数据导出
     */
    public void exportWithBigData(String fileName, Map<String, Object> conditions) {
        this.exportExcel(fileName, conditions);
    }

    /**
     * 获取导出的表头
     */
    @Override
    protected List<List<String>> getExcelHead() {
        List<List<String>> head = new ArrayList<>();
        //Collections.singletonList创建包含单个元素的不可变列表(不可修改的,即不能添加、删除或修改其中的元素)
        head.add(Collections.singletonList("用户编号"));
        head.add(Collections.singletonList("用户姓名"));
        return head;
    }

    /**
     * 计算导出数据的总数
     */
    @Override
    protected Long dataTotalCount(Map<String, Object> conditions) {
        return userService.queryCount(conditions);
    }

    /**
     * 每一个sheet存放的数据总数
     */
    @Override
    protected Long eachSheetTotalCount() {
        return 5000L;
    }

    /**
     * 每次写入sheet的总数
     */
    @Override
    protected Long eachTimesWriteSheetTotalCount() {
        return 2000L;
    }

    /**
     * 构建每次写入的数据
     * resultList 空集合,存放数据(测试只存入id和name)
     * queryCondition 查询条件
     * pageNo 本次查询的页码
     * pageSize 本次查询页容量
     */
    @Override
    protected void buildDataList(List<List<String>> resultList, Map<String, Object> queryCondition,
                                 Long pageNo, Long pageSize) {
        UserPageReq userPageReq = new UserPageReq();
        //如果queryCondition不为空,则设置查询条件
        userPageReq.setPageIndex(Integer.parseInt(pageNo.toString()));
        userPageReq.setPageSize(Integer.parseInt(pageSize.toString()));
        //将req转为dto

        PageResult<UserPo> userPage = userService.getUserPage(UserConvert.INSTANCE.convertPageReqToPageDto(userPageReq));
        //获取userpo对象集合
        List<UserPo> records = userPage.getRecords();
        if(!CollectionUtils.isEmpty(records)) {
            records.forEach(user -> { //测试,只存入id,name
                resultList.add(Arrays.asList(user.getId().toString(), user.getName()));
            });
        }
    }
}

四、service层根据查询条件获取结果个数方法

@Override
public Long queryCount(Map<String, Object> conditions) {
    UserPo userPo = new UserPo();
    //可以将conditions内的元素,赋值到userpo对象上
    return userMapper.count(userPo);
}
<select id="count" resultType="java.lang.Long" parameterType="com.ssm.user.entity.po.UserPo">
    select count(1) from user
    <where>
        <if test="id != null">id = #{id}</if>
        <if test="name != null and name != ''">and name = #{name}</if>
        <if test="age != null">and age = #{age}</if>
        <if test="createBy != null and createBy != ''">and create_by = #{createBy}</if>
        <if test="createTime != null">and create_time = #{createTime}</if>
        <if test="updateBy != null and updateBy != ''">and update_by = #{updateBy}</if>
        <if test="updateTime != null">and update_time = #{updateTime}</if>
        <if test="deleteFlag != null">and delete_flag = #{deleteFlag}</if>
        <if test="version != null">and version = #{version}</if>
    </where>
</select>

五、结果实现

package com.ssm.user.controller;

import com.ssm.user.excel.UserExcelExport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

@RestController
@Api(value = "用户数据导出")
@RequestMapping("UserExport")
@Slf4j
public class UserExcelExportController {

    @Resource
    private UserExcelExport userExcelExport;

    @ApiOperation(value = "导出excel")
        @GetMapping("exportData")
    public void exportData() {
        //设置查询条件
        Map<String, Object> map = new HashMap<>();
        userExcelExport.exportWithBigData("用户列表", map);
    }
}

image.png