阿里开源(EasyExcel):使用Java将数据导出为Excel表格、带样式----》java web下载 Excel文件

一、技术选型

ava解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到KB级别,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便

easyExcel官方GitHub地址:内有详细的讲解

小编最近在工作中刚好遇到了这么一个需求,将一系列数据导出为Excel表格。然后就开始百度,就看到了上面这段话。 经过小编对 poi 和 easyExcel 的demo的对比,决定使用easyExcel实现这个需求。

二、实现过程

1、导入依赖

建议去GitHub查看最新版本号:

         <!-- 阿里开源EXCEL -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>1.1.2-beta5</version>
        </dependency>

2、编写工具类 EasyExcelUtil

import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.metadata.Font;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.metadata.TableStyle;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.hanclouds.teamwork.entity.EasyExcelParams;
import org.apache.poi.ss.usermodel.IndexedColors;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author MouFangCai
 * @date 2019/7/23 11:48
 */
public class EasyExcelUtil {

    /**
     * 下载EXCEL文件2007版本
     *
     * @throws IOException IO异常
     */
    public static void exportExcel2007Format(EasyExcelParams excelParams) throws IOException {
        exportExcel(excelParams, ExcelTypeEnum.XLSX);
    }

    /**
     * 下载EXCEL文件2003版本
     *
     * @throws IOException IO异常
     */
    public static void exportExcel2003Format(EasyExcelParams excelParams) throws IOException {
        exportExcel(excelParams, ExcelTypeEnum.XLS);
    }

    /**
     * 根据参数和版本枚举导出excel文件
     *
     * @param excelParams 参数实体
     * @param typeEnum    excel类型枚举
     * @throws IOException
     */
    private static void exportExcel(EasyExcelParams excelParams, ExcelTypeEnum typeEnum) throws IOException {
        HttpServletResponse response = excelParams.getResponse();
        ServletOutputStream out = response.getOutputStream();

        // ExcelWriter提供了多种构造方式,可自行查看选择所需要的
        ExcelWriter writer = new ExcelWriter(null, out, typeEnum,
                true, excelParams.getWriteHandler());

        // 设置web下载等的信息
        prepareResponds(response, typeEnum);
        // 创建一个sheet
        Sheet sheet = new Sheet(1, 0, excelParams.getDataModelClazz());
        sheet.setSheetName(excelParams.getSheetName());

        // 设置列宽 设置每列的宽度
        Map<Integer,Integer> columnWidth = new HashMap<>(6);
        columnWidth.put(0,6666);
        columnWidth.put(1,5000);
        columnWidth.put(2,15000);
        columnWidth.put(3,3000);
        columnWidth.put(4,20000);
        columnWidth.put(5,10000);
        sheet.setColumnWidthMap(columnWidth);
        sheet.setAutoWidth(Boolean.TRUE);
        // 用于设置 表格样式
        sheet.setTableStyle(createTableStyle());
        // 写入Excel中,也提供了多个重载方法,根据需要选择
        writer.write(excelParams.getData(), sheet);

        // 根据数据展示需要,用于合并单元格
        List<int[]> mergeList = excelParams.getMergeList();
        if (mergeList != null && mergeList.size() > 0){
            for (int[] arr : mergeList) {
                // 待合并的单元格参数:开始的行数,结束的行数,开始的列数,结束的列数
                writer.merge(arr[0], arr[1], arr[2], arr[3]);
            }
        }
        writer.finish();
        out.flush();
    }

    /**
     * 将文件输出到浏览器(导出文件)
     *
     * @param response 响应
     * @param typeEnum excel类型
     */
    private static void prepareResponds(HttpServletResponse response,ExcelTypeEnum typeEnum) throws UnsupportedEncodingException {

        String fileName = new String((new SimpleDateFormat("MMddHHmm").format(new Date()))
                .getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
        response.setContentType("multipart/form-data");
        response.setHeader("Content-disposition", "attachment;filename=" + fileName + typeEnum.getValue());
    }

    /**
     * 表格样式:目前easyExcel的样式调整,仅支持表头和内容两部分别统一设置
     * @return
     */
    private static TableStyle createTableStyle(){
        TableStyle tableStyle = new TableStyle();
        tableStyle.setTableHeadBackGroundColor(IndexedColors.GREY_50_PERCENT);
        tableStyle.setTableContentBackGroundColor(IndexedColors.GREY_25_PERCENT);
        Font contentFont = new Font();
        contentFont.setFontName("黑体");
        contentFont.setFontHeightInPoints((short)12);
        tableStyle.setTableContentFont(contentFont);
        Font headFont = new Font();
        headFont.setFontName("黑体");
        headFont.setFontHeightInPoints((short)10);
        tableStyle.setTableHeadFont(headFont);
        return tableStyle;
    }

}

writer.merge(arr[0], arr[1], arr[2], arr[3]); 关于合并单元的这个merge方法

public ExcelWriter merge(int firstRow, int lastRow, int firstCol, int lastCol) {
    this.excelBuilder.merge(firstRow, lastRow, firstCol, lastCol);
    return this;
}

通过查看方法详情,即可理解:开始合并的行数,结束合并的行数,开始合并的列数,结束合并的列数

需要注意的是:对应的 last 的值必须大于等于 first 的值

3、公用参数类 EasyExcelParams

import com.alibaba.excel.metadata.BaseRowModel;
import com.alibaba.excel.metadata.Table;
import com.hanclouds.teamwork.util.MyWriteHandler;
import javax.servlet.http.HttpServletResponse;
import java.util.List;

/**
 * @author MouFangCai
 * @date 2019/7/23 20:18
 */
public class EasyExcelParams {

    /**
     * 表格样式:使用
     */
    private MyWriteHandler writeHandler;
    /**
     * 表格样式:未使用
     */
    private Table table;
    /**
     * 需合并的单元格参数配置集合 list
     */
    private List<int[]> mergeList;
    /**
     * excel文件名(不带拓展名)
     */
    private String excelNameWithoutExt;
    /**
     * sheet名称
     */
    private String sheetName;
    /**
     * 是否需要表头
     */
    private boolean needHead = true;
    /**
     * 数据
     */
    private List<? extends BaseRowModel> data;

    /**
     * 数据模型类型
     */
    private Class<? extends BaseRowModel> dataModelClazz;

    /**
     * 响应
     */
    private HttpServletResponse response;

    public EasyExcelParams() {
    }

    public MyWriteHandler getWriteHandler() {
        return writeHandler;
    }

    public void setWriteHandler(MyWriteHandler writeHandler) {
        this.writeHandler = writeHandler;
    }

    public Table getTable() {
        return table;
    }

    public void setTable(Table table) {
        this.table = table;
    }

    public List<int[]> getMergeList() {
        return mergeList;
    }

    public void setMergeList(List<int[]> mergeList) {
        this.mergeList = mergeList;
    }

    public String getExcelNameWithoutExt() {
        return excelNameWithoutExt;
    }

    public void setExcelNameWithoutExt(String excelNameWithoutExt) {
        this.excelNameWithoutExt = excelNameWithoutExt;
    }

    public String getSheetName() {
        return sheetName;
    }

    public void setSheetName(String sheetName) {
        this.sheetName = sheetName;
    }

    public boolean isNeedHead() {
        return needHead;
    }

    public void setNeedHead(boolean needHead) {
        this.needHead = needHead;
    }

    public List<? extends BaseRowModel> getData() {
        return data;
    }

    public void setData(List<? extends BaseRowModel> data) {
        this.data = data;
    }

    public Class<? extends BaseRowModel> getDataModelClazz() {
        return dataModelClazz;
    }

    public void setDataModelClazz(Class<? extends BaseRowModel> dataModelClazz) {
        this.dataModelClazz = dataModelClazz;
    }

    public HttpServletResponse getResponse() {
        return response;
    }

    public void setResponse(HttpServletResponse response) {
        this.response = response;
    }
}

4、表格样式实体类 MyWriteHandler

关于使用easyExcel去进行表格样式的设置,个人感觉对于复杂的样式成本是比较的,所以小编这里的样式,仅仅只有边框、水平居中、垂直居中、内容自适应。而且整个表格内容的格式是一样的。

package com.hanclouds.teamwork.util;

import com.alibaba.excel.event.WriteHandler;
import org.apache.poi.ss.usermodel.*;

/**
 * @author MouFangCai
 * @date 2019/7/28 18:06
 */
public class MyWriteHandler implements WriteHandler {
    private CellStyle cellStyle;
    @Override
    public void sheet(int i, Sheet sheet) {
        Workbook workbook = sheet.getWorkbook();
        // 创建样式
        cellStyle = workbook.createCellStyle();
    }

    @Override
    public void row(int i, Row row) {
    }

    @Override
    public void cell(int i, Cell cell) {
        if (cell.getRowIndex() >0) {
            createStyle();
            cell.setCellStyle(this.cellStyle);
        }
        }

    private void createStyle() {
          // 填充色:和EasyExcelUtil里的createTableStyle效果一样
//        cellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
//        cellStyle.setFillForegroundColor(HSSFColor.HSSFColorPredefined.BLUE.getIndex());
//        cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);

        // 下边框
        cellStyle.setBorderBottom(BorderStyle.THIN);
        // 左边框
        cellStyle.setBorderLeft(BorderStyle.THIN);
        // 右边框
        cellStyle.setBorderRight(BorderStyle.THIN);
        // 上边框
        cellStyle.setBorderTop(BorderStyle.THIN);
        // 设置自动换行
        cellStyle.setWrapText(true);
        // 水平对齐方式
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        cellStyle.setFillBackgroundColor((short)22);
        // 垂直对齐方式
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
    }

}

5、数据实体类(也是表头)BaseRowModel

注意,必须继承** BaseRowModel**

** @ExcelProperty(value = "部门",index = 0) 该注解的作用就是:声明表头名,和对应的位置,index=0表示在列数“第一列”**

package com.hanclouds.teamwork.entity;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.metadata.BaseRowModel;

/**
 * @author MouFangCai
 * @date 2019/7/24 9:18
 */

public class WeeklyExportDto extends BaseRowModel {

    @ExcelProperty(value = "部门",index = 0)
    private String groupName;

    @ExcelProperty(value = "项目名称",index = 1)
    private String projectName;

    @ExcelProperty(value = "任务名",index = 2)
    private String weeklyTaskName;

    @ExcelProperty(value = "责任人",index = 3)
    private String dutyUserName;

    @ExcelProperty(value = "完成情况、下周计划",index = 4)
    private String detail;

    @ExcelProperty(value = "任务描述",index = 5)
    private String taskDescription;

    public WeeklyExportDto() {
    }

    public String getGroupName() {
        return groupName;
    }

    public void setGroupName(String groupName) {
        this.groupName = groupName;
    }

    public String getProjectName() {
        return projectName;
    }

    public void setProjectName(String projectName) {
        this.projectName = projectName;
    }

    public String getWeeklyTaskName() {
        return weeklyTaskName;
    }

    public void setWeeklyTaskName(String weeklyTaskName) {
        this.weeklyTaskName = weeklyTaskName;
    }

    public String getDutyUserName() {
        return dutyUserName;
    }

    public void setDutyUserName(String dutyUserName) {
        this.dutyUserName = dutyUserName;
    }

    public String getDetail() {
        return detail;
    }

    public void setDetail(String detail) {
        this.detail = detail;
    }

    public String getTaskDescription() {
        return taskDescription;
    }

    public void setTaskDescription(String taskDescription) {
        this.taskDescription = taskDescription;
    }
}

6、主程序

数据结构说明:

参考:8、导出的Excel效果

待导出的数据结构如下:
---有多个分组:List<WeeklyGroupRes>
----一个分组下,有多个项目: List<WeeklyProjectDto> weeklyProjectList = weeklyGroupRes.getWeeklyProjectList();
----一个项目下,有多个任务: List<WeeklyItemDto> itemDtoList = weeklyProjectDto.getItemDtoList();

同一个分组,需合并单元格;同一个项目,需合并单元格。

 @GetMapping("/export")
 public boolean exportExcel( HttpServletResponse response) {

        // 先从数据库 获取 需要导出的数据(此处自查)
         List<WeeklyGroupRes> dataList = new ArrayList<>();
        // mergeList 用于存放合并单元格的配置信息
        // int[] 数据存放4个参数,分别对应merge方法的参数
        // (int firstRow, int lastRow, int firstCol, int lastCol)
        List<int[]> mergeLis =new ArrayList<>();
        // 用于存放 写入Excel的数据
        List<WeeklyExportDto> resultList = new ArrayList<>();
        // 用于生成merge
        int lastRow = 0;
        // 遍历处理需要导出的数据,下面就是小编对数据结构的遍历处理,可以忽略
        for (WeeklyGroupRes weeklyGroupRes : dataList) {
            int[] groupInt = new int[4];
            groupInt[0] = lastRow + 1;
            List<WeeklyProjectDto> weeklyProjectList = weeklyGroupRes.getWeeklyProjectList();

            for (WeeklyProjectDto weeklyProjectDto : weeklyProjectList) {
                int[] projectInt = new int[4];
                projectInt[0] = lastRow + 1;
                List<WeeklyItemDto> itemDtoList = weeklyProjectDto.getItemDtoList();

                for (WeeklyItemDto weeklyItemDto : itemDtoList) {
                    String detail = thisComplete + weeklyItemDto.getComplete()
                            + "\n" + nextPlan + weeklyItemDto.getPlan();
                    Task task = taskMap.get(weeklyItemDto.getTaskId());
                    WeeklyExportDto exportDto = new WeeklyExportDto();
                    exportDto.setGroupName(weeklyGroupRes.getMemberGroupName());
                    exportDto.setProjectName(weeklyProjectDto.getProjectName());
                    exportDto.setWeeklyTaskName(weeklyItemDto.getWeeklyTaskName());
                    exportDto.setDutyUserName(weeklyItemDto.getUserNickName());
                    exportDto.setDetail(detail);
                    exportDto.setTaskDescription(task.getDescription());
                    resultList.add(exportDto);
                }
                lastRow = lastRow + itemDtoList.size();
                projectInt[1] = lastRow;
                projectInt[2] = 1;
                projectInt[3] = 1;
                if (projectInt[0] != projectInt[1]) {
                    mergeLis.add(projectInt);
                }
            }
            groupInt[1] = lastRow;
            groupInt[2] = 0;
            groupInt[3] = 0;
            if (groupInt[0] != groupInt[1]) {
                mergeLis.add(groupInt);
            }
        }
        // 设置各种参数,用于导出Excel
        EasyExcelParams easyExcelParams = new EasyExcelParams();
        easyExcelParams.setSheetName("导出Excel");
        easyExcelParams.setResponse(response);
        easyExcelParams.setData(resultList);
        easyExcelParams.setDataModelClazz(WeeklyExportDto.class);
        easyExcelParams.setNeedHead(false);
        easyExcelParams.setMergeList(mergeLis);
        easyExcelParams.setWriteHandler(new MyWriteHandler());
        try {
            EasyExcelUtil.exportExcel2007Format(easyExcelParams);
        } catch (IOException e) {
            e.printStackTrace();
            throw new HanCloudsException(CommonErrorCode.INTERNAL_SERVER_ERROR.getErrorCode(),
                    "Export failed. Please try again");
        }
        return true;
    }

7、运行程序,效果如下:

8、导出的Excel效果

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 通过Java进行excel导出,听着似乎有那么一点点的意思,也貌似很简单的样子(实事也很简单),究竟该怎么操作呢?...
    Java高级架构狮阅读 676评论 0 1
  • 在管理一个系统时,总会有许多的数据,为了方便浏览查看数据,系统总会提供「导出Excel」的功能;有导出就有导入,在...
    Xuuuuucong阅读 90,779评论 14 36
  • 位于地下一层的右手边第二家餐厅,夏松屋是个日料店。这片位于高级写字楼中间的地下餐饮街档次不低。比夏松屋更靠门口的是...
    cccxccc阅读 124评论 0 1
  • 2018,12.25 星期二 晴 68篇 今天早上早早起床给孩子烙油饼,可能是我的声音大了点,儿子也早...
    管西彩阅读 201评论 0 1
  • 正文:【学员信息】:1班2组-盛二八心-212 【作业要求】:结合课程内容进行个人定位 【...
    盛二八心阅读 242评论 2 4