文章目录

  • 1.业务背景
  • 2.解决思路
  • 3.解决方案
  • 3.1 引入依赖
  • 3.2 详细实现
  • 3.3 结果测试
  • 4.优缺分析


1.业务背景

最近公司有一个业务就是要求导出一个excel,虽说这个excel的表头只有一行,但是这一行的表头是由两部分组成,
一部分是固定部分,一部分是动态部分。要求导出的表头如下图所示,该表中前五列是固定表头,后面的列数不固定,
有可能是五个月,六个月或者八个月,九个月都有可能。

java excel多行表头_java excel多行表头

2.解决思路

因为笔者目前只掌握了Easyexcel的固定表头(也就是固定列)的导出,所以这里没有像用easy excel一样把该
excel每一行的数据抽象成一个对象去处理。所以这里用到的技术是poi,然后主要是通过哪一行哪一列来确定一个
单元格,比如一行一列确定了一个单元格,然后把该行该列的数据作为条件去查对应单元格里面的数据。

3.解决方案

3.1 引入依赖

//因为easyexcel底层用的也是poi,所以引入这个类就等于引入了poi
<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>3.1.3</version>
</dependency>

3.2 详细实现

public void dateConvertDemoFour(HttpServletResponse response) throws IOException {
        //经过查阅相关资料,从HSSFWorkbook和XSSFWorkbook还有SXSSFWorkbook中选出了SXSSFWorkbook作为适合笔者需求的实现类
        int rowAccess = 100;
        SXSSFWorkbook wb = new SXSSFWorkbook(rowAccess);
        
        //设置表头样式
        CellStyle cellStyleHeader = getCellStyle(wb);

        //创建第一个sheet
        SXSSFSheet sheetOne = wb.createSheet();
        wb.setSheetName(0, "第一个sheet页");

        //创建第二个sheet
        Sheet sheetTwo = wb.createSheet();
        wb.setSheetName(1, "第二个sheet页");

        /**
         * 一、确定第一个sheet表头(第二个sheet类似)
         */
        List<String> headList = getHeaderData(cellStyleHeader, sheetOne);

        /**
         * 二、填充第一个sheet数据(第二个sheet类似)
         */
        getContentData(sheetOne, headList);

        response.setCharacterEncoding("UTF-8");
        response.setHeader("content-Type", "application/vnd.ms-excel");
        response.setHeader("Content-Disposition", "attachment;filename=" + 
        URLEncoder.encode("demo" + "." + "xlsx", "UTF-8"));
        // 输出
        wb.write(response.getOutputStream());
    }

    /**
     * 设置表头数据
     *
     * @param wb
     * @return
     */
    private CellStyle getCellStyle(SXSSFWorkbook wb) {
        CellStyle cellStyleHeader = wb.createCellStyle();
        // 标题水平居中
        cellStyleHeader.setAlignment(HorizontalAlignment.CENTER);
        // 标题垂直居中
        cellStyleHeader.setVerticalAlignment(VerticalAlignment.CENTER);
        // 字体加粗
        Font font = wb.createFont();
        font.setBold(true);
        // 字体大小
        font.setFontHeight((short) 300);
        font.setFontName("宋体");
        cellStyleHeader.setFont(font);
        return cellStyleHeader;
    }
    
    
     	/**
         * 一、确定第一个sheet表头(第二个sheet类似)
         */
    private List<String> getHeaderData(CellStyle cellStyleHeader, SXSSFSheet sheetOne) {
        //确定行:假如第一个sheet有4行(其中有一行是表头行),其余需要从库中查出数据
        //确定列:假如第一个sheet有8列,需要从库中查出数据
        //假如有3行,8列,其中8是固定的5列加上时间的列数3(当然这里3是动态的,可以是7或者8或者9,这里是演示,所以写为3)
        int timeCount = 5;
        Row row = sheetOne.createRow(0);
        List<String> headList = new ArrayList<>();
        //确定首行中的固定列,也就是表头固定列(excel中列是从第0列开始的)
        for (int j = 0; j < timeCount; j++) {
            Cell cell = row.createCell(j);
            //设置表头样式
            cell.setCellStyle(cellStyleHeader);
            sheetOne.setColumnWidth(j, 25 * 140);
            if (j == 0) {
                cell.setCellValue("固定列名1");
            } else if (j == 1) {
                cell.setCellValue("固定列名2");
            } else if (j == 2) {
                cell.setCellValue("固定列名3");
            } else if (j == 3) {
                cell.setCellValue("固定列名4");
            } else {
                cell.setCellValue("固定列名5");
            }
        }
        //确定首行中的动态列,也就是表头动态列,模拟动态月份的表头(假如有3个月)
        int trendsCount = 3;
        for (int a = 5; a < 5 + trendsCount; a++) {
            Cell cell = row.createCell(a);
            //设置表头样式
            cell.setCellStyle(cellStyleHeader);
            //设置每一列的宽度(每列的宽度只需要在首行中设置就行)
            sheetOne.setColumnWidth(a, 25 * 140);
            String value = "2022-" + a;
            cell.setCellValue(value);
            //注意此处会在填充数据时会用到
            headList.add(value);
        }
        return headList;
    }

		/**
         * 二、填充第一个sheet数据(第二个sheet类似)
         */
    private void getContentData(SXSSFSheet sheetOne, List<String> headList) {
        //开始填充数据
        for (int rowNum = 1; rowNum < 4; rowNum++) {
            Row dataRow = sheetOne.createRow(rowNum);
            //1.填充固定列数据(固定列数据可以抽象为一个对象),此处为造的假数据
            ExcelTable excelTableOne = new ExcelTable("第一列数据", "第二列数据", "第三列数据", "第四列数据", "第五列数据");
            ExcelTable excelTableTwo = new ExcelTable("第一列数据", "第二列数据", "第三列数据", "第四列数据", "第五列数据");
            ExcelTable excelTableThree = new ExcelTable("第一列数据", "第二列数据", "第三列数据", "第四列数据", "第五列数据");
            List<ExcelTable> list = new ArrayList<>();
            list.add(excelTableOne);
            list.add(excelTableTwo);
            list.add(excelTableThree);
            for (int i = 0; i < 5; i++) {
                Cell cell = dataRow.createCell(i);
                if (i == 0) {
                    cell.setCellValue(list.get(rowNum - 1).getFixedColOne());
                } else if (i == 1) {
                    cell.setCellValue(list.get(rowNum - 1).getFixedColTwo());
                } else if (i == 2) {
                    cell.setCellValue(list.get(rowNum - 1).getFixedColThree());
                } else if (i == 3) {
                    cell.setCellValue(list.get(rowNum - 1).getFixedColFour());
                } else {
                    cell.setCellValue(list.get(rowNum - 1).getFixedColFive());
                }
            }
            //2.填充动态列数据(注意这里的起始列是固定列的最后一行加1)
            for (int i = 5; i < headList.size() + 5; i++) {
                Cell cell = dataRow.createCell(i);
                //此时需要把这一行的第一个元素:(String fixedColOne = list.get(rowNum).getFixedColOne();)
                //和这一列的元素:i = 5....都拿出来作为条件去数据库中查到这行这列确定的单元格的内容,然后进行填充
                //查db
//                cell.setCellValue("库中查到的数据");
                cell.setCellValue("第" + ((i + 1) + "列数据"));
            }


        }
    }

3.3 结果测试

java excel多行表头_java excel多行表头_02

  • 1 实现了固定表头+动态表头
  • 2 实现了固定表头对应的固定列数据的填充
  • 3 实现了动态表头对应的动态列数据的填充

4.优缺分析

优点:可以动态的实现数据的填充,不用拘泥于格式的多样性。因为该方法时针对每一个单元格进行操作的。
缺点:当数据量大的时候会影响导出速度,导致导出速度变慢。题外话:笔者在导出的时候,还特意 调整了网关的响应时长,不然会响应超时,所以各位请慎重选择此方法。或者有大佬能对此进行优化,欢迎指出,不胜感激。