Hutool Java 工具类库导出 Excel并合并数据,全网最详细!


ps:基于HuTool工具类ExcelWriter合并单元格并且使用 jdk1.8 lambda表达式

一、原始数据模板

二、合并后的数据

按照班级名称、班级分数、小组名称、小组得分、人物名称、人物总分进行单元格合并

合并后效果:


三、导入依赖
ps:pom依赖版本不合适可以换其他版本

导出是项目中最常见的功能,例如考勤记录导出,账单明细导出,订单记录导出等等。导出的工具类有许多种,目前常见的有poi,easypoi,poi...,今天我要说的是基于hutool-poi的导出,hutool-poi是将poi做了封装,简化了大量的代码编写。
使用方式:
maven
在项目的pom.xml中引入

    cn.hutool
    hutool-all
    4.5.1

gradle
在项目的build.gradle中引入
compile 'cn.hutool:hutool-all:5.7.3'


四、代码逻辑

1.查找数据库返回数据voList;
2.设置导出表头数据;
3.用lambda表达式获取字段分组数据;
4.遍历数据,设置合并规则;
5.将数据保存在list中;
6.ExcelWriter导出excel文件

五、代码
1.需实现将人物信息导出到excel文件中并且还要按照班级名称,小组名称,人物名称动态合并单元格,所以先创建人物对象:

@Data
@AllArgsConstructor //生成所有参数的构造器
public class Person {
    //班级名称
    private String className;
    //班级分数
    private double classScore;
    //小组名称
    private String groupName;
    //小组分数
    private double groupScore;
    //人物姓名
    private String personName;
    //人物总分
    private double personScore;
    //学科名称
    private String subjectName;
    //学科分数
    private double subjectScore;
}


2.导出核心代码:

@Slf4j
@RestController
@RequestMapping("/person")
public class PersonController {
    /**
     * 将页面的数据导出并合并单元格
     * @param response
     */
    @ApiOperation(value = "导出数据到excel")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "response", value = "响应体对象", dataType = "HttpServletResponse")
    })
    @GetMapping("/export")
    public void export(HttpServletResponse response)throws Exception{
        //1.模拟一些人物对象数据(工作中从数据库查出来)
        List list = new ArrayList<>();
        list.add(new Person("一班",90,"一组",89,"孙悟空",89,"语文",89));
        list.add(new Person("一班",90,"一组",89,"孙悟空",89,"数学",98));
        list.add(new Person("一班",90,"一组",89,"唐僧",78,"语文",98));
        list.add(new Person("一班",90,"一组",89,"唐僧",78,"数学",78));
        list.add(new Person("一班",90,"二组",90,"沙悟净",90,"语文",90));
        list.add(new Person("一班",90,"二组",90,"沙悟净",90,"数学",90));
        list.add(new Person("二班",91,"一组",97,"鲁智深",98,"语文",89));
        list.add(new Person("二班",91,"一组",97,"鲁智深",98,"数学",98));
        list.add(new Person("二班",91,"二组",89,"宋江",89,"语文",98));
        list.add(new Person("二班",91,"二组",89,"宋江",89,"数学",78));
        list.add(new Person("二班",91,"二组",89,"林冲",88,"语文",90));
        list.add(new Person("二班",91,"二组",89,"林冲",88,"数学",90));

        //2.定义基础数据
        List rowHead = CollUtil.newArrayList("班级名称","班级分数","小组名称","小组分数","角色姓名","角色总分","学科名称","学科分数");
        //3.通过ExcelUtil.getBigWriter()创建Writer对象,BigExcelWriter用于大数据量的导出,不会引起溢出;
        ExcelWriter writer = ExcelUtil.getBigWriter();
        //4.写入标题
        writer.writeHeadRow(rowHead);
        ServletOutputStream out = null;
        //5.实现核心逻辑
        try {
            //6.定义容器保存人物数据
            List> rows = new LinkedList<>();
            //7.按照班级进行分组
            LinkedHashMap> classList = list.stream().collect(Collectors.groupingBy(item -> item.getClassName(),
                    LinkedHashMap::new, Collectors.toList()));
            //8.定义起始行(方便分组后合并时从哪一行开始)
            //因为标题已经占了一行,所以数据从第二行开始写(excel第一行索引为0)
            //因需要合并到人物分数单元格所以需定义如下起始坐标
            int indexClassName = 1;   //班级名称起始行
            int indexClassScore = 1;
            int indexGroupName = 1;
            int indexGroupScore = 1;
            int indexPersonName = 1;
            int indexPersonScore = 1;
            //9.遍历按班级名分组后的list(用entrySet效率比keySet效率高)
            for (Map.Entry> classNameListEntry : classList.entrySet()) {
                //10.获取按照班级名分组后的集合
                List classValue = classNameListEntry.getValue();
                //11.计算此集合的长度
                int classSize = classValue.size();
                //12.如果只有一行数据不能调用merge方法合并数据,否则会报错
                if (classSize == 1){
                    indexClassName += classSize;
                    indexClassScore += classSize;
                    indexGroupName += classSize;
                    indexGroupScore += classSize;
                    indexPersonName += classSize;
                    indexPersonScore += classSize;
                }else{
                    //13.根据班级名称进行合并单元格
                    //合并行,第一个参数是合并行的开始行号(行号从0开始),第二个参数是合并行的结束行号,第三个参数是合并的列号开始(列号从0开始),
                    //第四个参数是合并的列号结束,第五个参数是合并后的内容,null不设置,第六个参数指是否支持设置样式,true指的是。
                    writer.merge(indexClassName, indexClassName + classSize - 1, 0, 0, null, true);
                    //14.合并完后起始索引移到下一个合并点
                    indexClassName += classSize;
                    //15.因为班级分数与班级名称相关联,所以不需要对班级分数分组,直接在此基础上对班级分数合并
                    writer.merge(indexClassScore, indexClassScore + classSize - 1, 1, 1, null, true);
                    indexClassScore += classSize;
                    //16.按照小组名进行分组(以下分组与班级名合并类似)
                    LinkedHashMap> groupList = classValue.stream().collect(Collectors.groupingBy(item -> item.getGroupName(),
                            LinkedHashMap::new, Collectors.toList()));
                    for (Map.Entry> groupListEntry : groupList.entrySet()) {
                        List groupValue = groupListEntry.getValue();
                        int groupSize = groupValue.size();
                        if (groupSize == 1){
                            indexGroupName += groupSize;
                            indexGroupScore += groupSize;
                            indexPersonName += groupSize;
                            indexPersonScore += groupSize;
                        }else{
                            //合并小组
                            writer.merge(indexGroupName, indexGroupName + groupSize - 1, 2, 2, null, true);
                            indexGroupName += groupSize;
                            //合并小组分数
                            writer.merge(indexGroupScore, indexGroupScore + groupSize - 1, 3, 3, null, true);
                            indexGroupScore += groupSize;
                            //17.按照人物名称进行分组
                            LinkedHashMap> personList = groupValue.stream().collect(Collectors.groupingBy(item -> item.getPersonName(),
                                    LinkedHashMap::new, Collectors.toList()));
                            for (Map.Entry> personListEntry : personList.entrySet()) {
                                List personValue = personListEntry.getValue();
                                int personSize = personValue.size();
                                if (personSize == 1){
                                    indexPersonName += personSize;
                                    indexPersonScore += personSize;
                                }else{
                                    //合并人物
                                    writer.merge(indexPersonName, indexPersonName + personSize - 1, 4, 4, null, true);
                                    indexPersonName += personSize;
                                    //合并人物总分
                                    writer.merge(indexPersonScore, indexPersonScore + personSize - 1, 5, 5, null, true);
                                    indexPersonScore += personSize;
                                }
                            }
                        }
                    }

                }
                //18.保存数据
                classValue.forEach(
                        sList->{
                            List rowA = null;
                            rowA = CollUtil.newArrayList(
                                    sList.getClassName(),
                                    sList.getClassScore(),
                                    sList.getGroupName(),
                                    sList.getGroupScore(),
                                    sList.getPersonName(),
                                    sList.getPersonScore(),
                                    sList.getSubjectName(),
                                    sList.getSubjectScore()
                            );
                            rows.add(rowA);
                        }
                );
            }
            //19.导出数据
            //logger.info("导出数据:{}",rows.toString());
            // 一次性写出内容,使用默认样式,强制输出标题
            writer.write(rows, true);
            //response为HttpServletResponse对象
            response.setContentType("application/vnd.ms-excel;charset=utf-8");
            //获取当前日期作为文件名
            Date currentDate = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
            String date = sdf.format(currentDate);
            //test.xls是弹出下载对话框的文件名,不能为中文,中文请自行编码
            //response.setHeader("Content-Disposition", "attachment;filename=file.xlsx");
            response.setHeader("Content-Disposition", "attachment;filename=report_"+date+".xlsx");
            out = response.getOutputStream();
            writer.flush(out, true);
        }finally {
            //关闭输出Servlet流
            IoUtil.close(out);
            //关闭writer,释放内存
            writer.close();
        }
    }
}


六、测试

1.使用postman测试

2.结果:

借鉴地址:
https://juejin.cn/post/6982853657679101982

https://blog.csdn.net/zqy_CSDN_name/article/details/119643968