阿里開源(EasyExcel):使用Java將數(shù)據(jù)導(dǎo)出為Excel表格土匀、帶樣式----》java web下載 Excel文件

一子房、技術(shù)選型

ava解析、生成Excel比較有名的框架有Apache poi就轧、jxl证杭。但他們都存在一個(gè)嚴(yán)重的問題就是非常的耗內(nèi)存,poi有一套SAX模式的API可以一定程度的解決一些內(nèi)存溢出的問題妒御,但POI還是有一些缺陷解愤,比如07版Excel解壓縮以及解壓后存儲都是在內(nèi)存中完成的,內(nèi)存消耗依然很大乎莉。easyexcel重寫了poi對07版Excel的解析琢歇,能夠原本一個(gè)3M的excel用POI sax依然需要100M左右內(nèi)存降低到KB級別,并且再大的excel不會出現(xiàn)內(nèi)存溢出梦鉴,03版依賴POI的sax模式。在上層做了模型轉(zhuǎn)換的封裝揭保,讓使用者更加簡單方便

easyExcel官方GitHub地址:內(nèi)有詳細(xì)的講解

小編最近在工作中剛好遇到了這么一個(gè)需求肥橙,將一系列數(shù)據(jù)導(dǎo)出為Excel表格。然后就開始百度秸侣,就看到了上面這段話存筏。 經(jīng)過小編對 poi 和 easyExcel 的demo的對比宠互,決定使用easyExcel實(shí)現(xiàn)這個(gè)需求。

二椭坚、實(shí)現(xiàn)過程

1予跌、導(dǎo)入依賴

建議去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);
    }

    /**
     * 根據(jù)參數(shù)和版本枚舉導(dǎo)出excel文件
     *
     * @param excelParams 參數(shù)實(shí)體
     * @param typeEnum    excel類型枚舉
     * @throws IOException
     */
    private static void exportExcel(EasyExcelParams excelParams, ExcelTypeEnum typeEnum) throws IOException {
        HttpServletResponse response = excelParams.getResponse();
        ServletOutputStream out = response.getOutputStream();

        // ExcelWriter提供了多種構(gòu)造方式善茎,可自行查看選擇所需要的
        ExcelWriter writer = new ExcelWriter(null, out, typeEnum,
                true, excelParams.getWriteHandler());

        // 設(shè)置web下載等的信息
        prepareResponds(response, typeEnum);
        // 創(chuàng)建一個(gè)sheet
        Sheet sheet = new Sheet(1, 0, excelParams.getDataModelClazz());
        sheet.setSheetName(excelParams.getSheetName());

        // 設(shè)置列寬 設(shè)置每列的寬度
        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);
        // 用于設(shè)置 表格樣式
        sheet.setTableStyle(createTableStyle());
        // 寫入Excel中券册,也提供了多個(gè)重載方法,根據(jù)需要選擇
        writer.write(excelParams.getData(), sheet);

        // 根據(jù)數(shù)據(jù)展示需要垂涯,用于合并單元格
        List<int[]> mergeList = excelParams.getMergeList();
        if (mergeList != null && mergeList.size() > 0){
            for (int[] arr : mergeList) {
                // 待合并的單元格參數(shù):開始的行數(shù)烁焙,結(jié)束的行數(shù),開始的列數(shù)耕赘,結(jié)束的列數(shù)
                writer.merge(arr[0], arr[1], arr[2], arr[3]);
            }
        }
        writer.finish();
        out.flush();
    }

    /**
     * 將文件輸出到瀏覽器(導(dǎo)出文件)
     *
     * @param response 響應(yīng)
     * @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的樣式調(diào)整骄蝇,僅支持表頭和內(nèi)容兩部分別統(tǒng)一設(shè)置
     * @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]); 關(guān)于合并單元的這個(gè)merge方法

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

通過查看方法詳情,即可理解:開始合并的行數(shù)操骡,結(jié)束合并的行數(shù)九火,開始合并的列數(shù),結(jié)束合并的列數(shù)

需要注意的是:對應(yīng)的 last 的值必須大于等于 first 的值

3册招、公用參數(shù)類 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;
    /**
     * 需合并的單元格參數(shù)配置集合 list
     */
    private List<int[]> mergeList;
    /**
     * excel文件名(不帶拓展名)
     */
    private String excelNameWithoutExt;
    /**
     * sheet名稱
     */
    private String sheetName;
    /**
     * 是否需要表頭
     */
    private boolean needHead = true;
    /**
     * 數(shù)據(jù)
     */
    private List<? extends BaseRowModel> data;

    /**
     * 數(shù)據(jù)模型類型
     */
    private Class<? extends BaseRowModel> dataModelClazz;

    /**
     * 響應(yīng)
     */
    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岔激、表格樣式實(shí)體類 MyWriteHandler

關(guān)于使用easyExcel去進(jìn)行表格樣式的設(shè)置,個(gè)人感覺對于復(fù)雜的樣式成本是比較的跨细,所以小編這里的樣式鹦倚,僅僅只有邊框、水平居中冀惭、垂直居中震叙、內(nèi)容自適應(yīng)。而且整個(gè)表格內(nèi)容的格式是一樣的散休。

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();
        // 創(chuàng)建樣式
        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);
        // 設(shè)置自動(dòng)換行
        cellStyle.setWrapText(true);
        // 水平對齊方式
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        cellStyle.setFillBackgroundColor((short)22);
        // 垂直對齊方式
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
    }

}

5媒楼、數(shù)據(jù)實(shí)體類(也是表頭)BaseRowModel

注意,必須繼承** BaseRowModel**

** @ExcelProperty(value = "部門",index = 0) 該注解的作用就是:聲明表頭名戚丸,和對應(yīng)的位置划址,index=0表示在列數(shù)“第一列”**

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 = "項(xiàng)目名稱",index = 1)
    private String projectName;

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

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

    @ExcelProperty(value = "完成情況、下周計(jì)劃",index = 4)
    private String detail;

    @ExcelProperty(value = "任務(wù)描述",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限府、主程序

數(shù)據(jù)結(jié)構(gòu)說明:

參考:8夺颤、導(dǎo)出的Excel效果

待導(dǎo)出的數(shù)據(jù)結(jié)構(gòu)如下:
---有多個(gè)分組:List<WeeklyGroupRes>
----一個(gè)分組下,有多個(gè)項(xiàng)目: List<WeeklyProjectDto> weeklyProjectList = weeklyGroupRes.getWeeklyProjectList();
----一個(gè)項(xiàng)目下胁勺,有多個(gè)任務(wù): List<WeeklyItemDto> itemDtoList = weeklyProjectDto.getItemDtoList();

同一個(gè)分組世澜,需合并單元格;同一個(gè)項(xiàng)目署穗,需合并單元格寥裂。

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

        // 先從數(shù)據(jù)庫 獲取 需要導(dǎo)出的數(shù)據(jù)(此處自查)
         List<WeeklyGroupRes> dataList = new ArrayList<>();
        // mergeList 用于存放合并單元格的配置信息
        // int[] 數(shù)據(jù)存放4個(gè)參數(shù)嵌洼,分別對應(yīng)merge方法的參數(shù)
        // (int firstRow, int lastRow, int firstCol, int lastCol)
        List<int[]> mergeLis =new ArrayList<>();
        // 用于存放 寫入Excel的數(shù)據(jù)
        List<WeeklyExportDto> resultList = new ArrayList<>();
        // 用于生成merge
        int lastRow = 0;
        // 遍歷處理需要導(dǎo)出的數(shù)據(jù),下面就是小編對數(shù)據(jù)結(jié)構(gòu)的遍歷處理,可以忽略
        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);
            }
        }
        // 設(shè)置各種參數(shù)封恰,用于導(dǎo)出Excel
        EasyExcelParams easyExcelParams = new EasyExcelParams();
        easyExcelParams.setSheetName("導(dǎo)出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麻养、運(yùn)行程序,效果如下:

8诺舔、導(dǎo)出的Excel效果

?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鳖昌,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子混萝,更是在濱河造成了極大的恐慌遗遵,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逸嘀,死亡現(xiàn)場離奇詭異车要,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)崭倘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門翼岁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人司光,你說我怎么就攤上這事琅坡。” “怎么了残家?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵榆俺,是天一觀的道長。 經(jīng)常有香客問我坞淮,道長茴晋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任回窘,我火速辦了婚禮诺擅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘啡直。我一直安慰自己烁涌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布酒觅。 她就那樣靜靜地躺著撮执,像睡著了一般。 火紅的嫁衣襯著肌膚如雪舷丹。 梳的紋絲不亂的頭發(fā)上抒钱,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼继效。 笑死,一個(gè)胖子當(dāng)著我的面吹牛装获,可吹牛的內(nèi)容都是我干的瑞信。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼穴豫,長吁一口氣:“原來是場噩夢啊……” “哼凡简!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起精肃,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤秤涩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后司抱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體筐眷,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年习柠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了匀谣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,127評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡资溃,死狀恐怖武翎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情溶锭,我是刑警寧澤宝恶,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站趴捅,受9級特大地震影響垫毙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜驻售,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一露久、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧欺栗,春花似錦毫痕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至类腮,卻和暖如春臊泰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蚜枢。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工缸逃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留针饥,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓需频,卻偏偏與公主長得像丁眼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子昭殉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容