JAVA操作Excel(POI、easyPOI、easyExcel)

导读:本篇文章讲解 JAVA操作Excel(POI、easyPOI、easyExcel),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

POI、easyPOI、easyExcel的使用

我只是讲解最平常的使用技巧,实战中会遇到格式各样的情况,看官方文档、Debug、搜索报错信息,是最适合学习和解决问题的途径,使用官方文档写在了每一条讲解的下方,供大家快速预览

在讲解之前先来聊一下03和07版的Excel

一、支持文档类型不同

1、Excel03:只支持xls类型的文档。

2、Excel07:除了支持xls类型文档,还支持xlsx类型的文档。

二、功能不同

1、Excel03:将智能标记操作与部分电子表格的特定内容关联,并使适当的智能标记操作仅在用户将鼠标悬停在关联的单元格区域上时出现。

2、Excel07:菜单、工具条已经成为历史,取而代之的是功能区,大量图标和命令组织到多个选项卡中,形成带状区域,还有快速访问工具栏、画廊等。

三、特点不同

1、Excel03:使用 Excel 2003 在任何客户定义的 XML 架构中读取数据。在基础 XML 数据存储发生更改时更新图表、表格和曲线图。

2、Excel07:文件都是基于XML格式。新的SmartArt工具,为Office文档创建清晰的过程图、流程图和其他业务相关的图表。

在我们使用代码来操作Excel,03与07版最大的不同就是==2003 版本和 2007 版本存在兼容性的问题!03最多只有 65535 行!==而07版本理论上是没有限制的

POI讲解(基础)

ApachePOI官网

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3YQajOTG-1658855178613)(C:\Users\18358\AppData\Roaming\Typora\typora-user-images\image-20220727001505305.png)]

导包

 <!--导入依赖-->
    <dependencies>
        <!--xls(03)-->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>5.2.2</version>
        </dependency>

        <!--xlsx(07)-->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.2.2</version>
        </dependency>

        <!--日期格式化工具-->
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.10.14</version>
        </dependency>

        <!--test-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

读写操作(03 | 07 版本的写,就是对象不同,方法一样的!)03的对象是 HSSF ;07的对象是 XSSF

public class ExcelWriteTest {

    String PATH = "D:\\idea\\project\\POI_and_easyExcel_study\\01_POI_study\\src\\main\\java\\com\\";

    //03版本
    @Test
    public void testWrite03() throws IOException {
        //1.创建一个工作簿
        Workbook workbook = new HSSFWorkbook();
        //2.创建一个工作表
        Sheet sheet = workbook.createSheet("随便写点");
        //3、创建一个行(第一行)
        Row row = sheet.createRow(0);
        //4、创建一个单元格
        Cell cell11 = row.createCell(0);
        cell11.setCellValue("!!!!!!!!!!!!!!");
        //两个格子填充值
        Cell cell12 = row.createCell(1);
        cell12.setCellValue("@@@@@@@@@@@@@@@");

        //第二行
        Row row2 = sheet.createRow(1);
        Cell cell21 = row2.createCell(0);
        cell21.setCellValue("########################");
        //(2,2)
        Cell cell22 = row2.createCell(1);
        String time = new DateTime().toString("yyyy-MM-dd HH:mm:ss");
        cell22.setCellValue(time);

        //生成一张表(IO流)  03 版本就是使用xls结尾
        FileOutputStream fileOutputStream = new FileOutputStream(PATH + "03.xls");

        workbook.write(fileOutputStream);
        //关闭流
        fileOutputStream.close();

        System.out.println("生成完毕");
    }
    //07版本
    @Test
    public void testWrite2() throws IOException {
        //1.创建一个工作簿
        Workbook workbook = new XSSFWorkbook();
        //2.创建一个工作表
        Sheet sheet = workbook.createSheet("随便写点");
        //3、创建一个行(第一行)
        Row row = sheet.createRow(0);
        //4、创建一个单元格
        Cell cell11 = row.createCell(0);
        cell11.setCellValue("!!!!!!!!!!!!!!");
        //两个格子填充值
        Cell cell12 = row.createCell(1);
        cell12.setCellValue("@@@@@@@@@@@@@@@");

        //第二行
        Row row2 = sheet.createRow(1);
        Cell cell21 = row2.createCell(0);
        cell21.setCellValue("########################");
        //(2,2)
        Cell cell22 = row2.createCell(1);
        String time = new DateTime().toString("yyyy-MM-dd HH:mm:ss");
        cell22.setCellValue(time);

        //生成一张表(IO流)  03 版本就是使用xlsx结尾
        FileOutputStream fileOutputStream = new FileOutputStream(PATH + "07.xlsx");

        workbook.write(fileOutputStream);
        //关闭流
        fileOutputStream.close();

        System.out.println("生成完毕");
    }

    @Test
    public void testWrite03BigData() throws IOException {
        // 时间差(开始)
        long begin = System.currentTimeMillis();

        //创建一个簿
        Workbook workbook = new HSSFWorkbook();
        //创建表
        Sheet sheet = workbook.createSheet();
        //写入数据
        for (int rowNum = 0; rowNum < 65536; rowNum++) {
            Row row = sheet.createRow(rowNum);
            for (int cellMum = 0; cellMum < 10; cellMum++) {
                Cell cell = row.createCell(cellMum);
                cell.setCellValue(cellMum);
            }
        }
        System.out.println("over");
        FileOutputStream fileOutputStream = new FileOutputStream(PATH + "BigData03.xls");
        workbook.write(fileOutputStream);
        fileOutputStream.close();

        // 时间差(结束)
        long end = System.currentTimeMillis();

        System.out.println("结束时间:"+(double)(end-begin)/1000);

    }


    //耗时较长
    @Test
    public void testWrite07BigData() throws IOException {
        // 时间差(开始)
        long begin = System.currentTimeMillis();

        //创建一个簿
        Workbook workbook = new XSSFWorkbook();
        //创建表
        Sheet sheet = workbook.createSheet();
        //写入数据
        for (int rowNum = 0; rowNum < 65536; rowNum++) {
            Row row = sheet.createRow(rowNum);
            for (int cellMum = 0; cellMum < 10; cellMum++) {
                Cell cell = row.createCell(cellMum);
                cell.setCellValue(cellMum);
            }
        }
        System.out.println("over");
        FileOutputStream fileOutputStream = new FileOutputStream(PATH + "BigData07.xlsx");
        workbook.write(fileOutputStream);
        fileOutputStream.close();

        // 时间差(结束)
        long end = System.currentTimeMillis();

        System.out.println("结束时间:"+(double)(end-begin)/1000);
    }

    //升级版
    @Test
    public void testWrite07BigDataS() throws IOException {
        // 时间差(开始)
        long begin = System.currentTimeMillis();

        //创建一个簿
        SXSSFWorkbook workbook = new SXSSFWorkbook();
        //创建表
        Sheet sheet = workbook.createSheet();
        //写入数据
        for (int rowNum = 0; rowNum < 65536; rowNum++) {
            Row row = sheet.createRow(rowNum);
            for (int cellMum = 0; cellMum < 10; cellMum++) {
                Cell cell = row.createCell(cellMum);
                cell.setCellValue(cellMum);
            }
        }
        System.out.println("over");
        FileOutputStream fileOutputStream = new FileOutputStream(PATH + "BigDataS07.xlsx");
        workbook.write(fileOutputStream);
        fileOutputStream.close();
        //清除临时文件
        workbook.dispose();

        // 时间差(结束)
        long end = System.currentTimeMillis();

        System.out.println("结束时间:"+(double)(end-begin)/1000);
    }
}

public class ExcelReadTest {

    String PATH = "D:\\idea\\project\\POI_and_easyExcel_study\\01_POI_study\\src\\main\\java\\com\\";
    //03版本
    @Test
    public void testRed03() throws IOException {
        //获取文件流
        FileInputStream fileInputStream = new FileInputStream(PATH + "03.xls");

        //1.创建一个工作簿。 使用excel能操作的这边都能操作!
        Workbook workbook = new HSSFWorkbook(fileInputStream);
        //2.得到表
        Sheet sheetAt = workbook.getSheetAt(0);
        //3.得到行
        Row row = sheetAt.getRow(0);
        //4.得到列
        Cell cell = row.getCell(0);

        // 读取值得到时候,一定要注意类型!
        //getStringCellValue() 获取字符串
        //getNumericCellValue() 获取数字
        System.out.println(cell.getStringCellValue());
        fileInputStream.close();

    }
    
    //07版本
    @Test
    public void testWrite07() throws IOException {
        // 创建新的Excel 工作簿, 只有对象变了
        Workbook workbook = new XSSFWorkbook();
        // 如要新建一名为"会员登录统计"的工作表,其语句为:
        Sheet sheet = workbook.createSheet("会员登录统计");
        // 创建行(row 1)
        Row row1 = sheet.createRow(0);
        // 创建单元格(col 1-1)
        Cell cell11 = row1.createCell(0);
        cell11.setCellValue("今日新增关注");
        // 创建单元格(col 1-2)
        Cell cell12 = row1.createCell(1);
        cell12.setCellValue(666);
        // 创建行(row 2)
        Row row2 = sheet.createRow(1);
        // 创建单元格(col 2-1)
        Cell cell21 = row2.createCell(0);
        cell21.setCellValue("统计时间");
        //创建单元格(第三列)
        Cell cell22 = row2.createCell(1);
        String dateTime = new DateTime().toString("yyyy-MM-dd HH:mm:ss");
        cell22.setCellValue(dateTime);
        // 新建一输出文件流(注意:要先创建文件夹)
        FileOutputStream out = new FileOutputStream(PATH + "07.xlsx");
        // 把相应的Excel 工作簿存盘
        workbook.write(out);
        // 操作结束,关闭文件    
        out.close();
        System.out.println("文件生成成功");
    }
    
    //判断类型读取
    @Test
    public void testCellType() throws IOException{

        //获取文件流
        FileInputStream fileInputStream = new FileInputStream(PATH + "03.xls");

        //1.创建一个工作簿。 使用excel能操作的这边都能操作!
        Workbook workbook = new HSSFWorkbook(fileInputStream);
        Sheet sheetAt = workbook.getSheetAt(0);
        //获取标题内容
        Row rowTitle = sheetAt.getRow(0);
        if (rowTitle!=null){
            // 一点要掌握,获取到一行后,通过循环遍历每一个单元格
            int cellCount = rowTitle.getPhysicalNumberOfCells();
            for (int cellNum = 0; cellNum < cellCount; cellNum++) {
                Cell cell = rowTitle.getCell(cellNum);
                if (cell!=null){
                    CellType cellType = cell.getCellType();
                    String cellValue = "";
                    switch (cellType){
                        case STRING:
                            System.out.print("【String】");
                            cellValue = cell.getStringCellValue();
                            break;
                        case BOOLEAN:
                            System.out.print("【BOOLEAN】");
                            cellValue = String.valueOf(cell.getBooleanCellValue());
                            break;
                        case BLANK:
                            System.out.print("【BLANK】");
                            break;
                        case NUMERIC:     //数字(日期、普通数字)
                            System.out.print("【NUMERIC】");
                            if (DateUtil.isCellDateFormatted(cell)){ //日期
                                System.out.print("【日期】");
                                Date date = cell.getDateCellValue();
                                cellValue = new DateTime(date).toString("yyyy-MM-dd");
                            }else {
                                //不是日期格式,防止数字过长!
                                System.out.print("【数字转化为字符串输出】");
                                cell.setCellType(CellType.STRING);
                                cellValue = cell.toString();
                            }
                            break;
                        case ERROR:
                            System.out.print("【数据类型错误】");
                            break;
                    }
                    System.out.println(cellValue);
                }
            }
            fileInputStream.close();
            System.out.println();
        }
        //获取表中的内容(获取多少行)
        int rowCount = sheetAt.getPhysicalNumberOfRows();
        for (int rowNum = 1; rowNum < rowCount; rowNum++) {
            Row row = sheetAt.getRow(rowNum);
            if (row!=null){
                assert rowTitle != null;
                int cellCount = rowTitle.getPhysicalNumberOfCells();
                for (int cellNum = 0; cellNum < cellCount; cellNum++) {
                    System.out.print("["+(rowNum+1)+"-"+(cellNum+1)+"]");
                }
            }
        }

    }
}

计算公式

@Test
public void testFormula() throws Exception{
    InputStream is = new FileInputStream(path + "计算公式.xls");
    Workbook workbook = new HSSFWorkbook(is);
    Sheet sheet = workbook.getSheetAt(0);
    // 读取第五行第一列
    Row row = sheet.getRow(4);
    Cell cell = row.getCell(0);
    //公式计算器
    FormulaEvaluator formulaEvaluator = new HSSFFormulaEvaluator((HSSFWorkbook) workbook);
    // 输出单元内容
    int cellType = cell.getCellType();
    switch (cellType) {
        case Cell.CELL_TYPE_FORMULA://2
            //得到公式
            String formula = cell.getCellFormula();
            System.out.println(formula);
            CellValue evaluate = formulaEvaluator.evaluate(cell);
            //String cellValue = String.valueOf(evaluate.getNumberValue());
            String cellValue = evaluate.formatAsString();
            System.out.println(cellValue);
            break;
    }
}

easyPOI讲解(推荐)

easyPOI官方文档

悟耘开源 / easypoi(gitee)

导包(普通maven项目)

    <dependencies>
        <dependency>
            <groupId>cn.afterturn</groupId>
            <artifactId>easypoi-base</artifactId>
            <version>4.4.0</version>
        </dependency>
        <dependency>
            <groupId>cn.afterturn</groupId>
            <artifactId>easypoi-web</artifactId>
            <version>4.4.0</version>
        </dependency>
        <dependency>
            <groupId>cn.afterturn</groupId>
            <artifactId>easypoi-annotation</artifactId>
            <version>4.4.0</version>
        </dependency>
        <!--        我这里用了lombok,根据个人情况选择是否使用,此依赖与easypoi无关,只是许需要写get set方法了-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
        </dependency>
    </dependencies>

springboot导包

        <!--easypoi-->
        <dependency>
            <groupId>cn.afterturn</groupId>
            <artifactId>easypoi-spring-boot-starter</artifactId>
            <version>4.4.0</version>
        </dependency>
        <!--下面这是普通maven要到的包-->
<!--        <dependency>-->
<!--            <groupId>cn.afterturn</groupId>-->
<!--            <artifactId>easypoi-base</artifactId>-->
<!--            <version>4.4.0</version>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>cn.afterturn</groupId>-->
<!--            <artifactId>easypoi-web</artifactId>-->
<!--            <version>4.4.0</version>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>cn.afterturn</groupId>-->
<!--            <artifactId>easypoi-annotation</artifactId>-->
<!--            <version>4.4.0</version>-->
<!--        </dependency>-->

我写了两个测试导入导出的Demo,但这两个Demo操作的不是同一个文件,需要自行修改,后面有实战,使用了工具类,比这两个测试Demo都要简单

测试Demo1(导入)

实体类(下面有专门对注解的解释)

StopWatchExport.java

/**
 * @Role 码表导出实体类
 * @Author 赵平安
 * @GMTCreate   2022年7月22日
 * @Modifier
 * @GMTModified
 * @ModifyContent
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class StopWatchExport {
    /**
     * 码表编号
     */
    @Excel(name="码表编号",  width = 30,needMerge = true)
    private String sCode;

    /**
     * 码表名称
     */
    @Excel(name="码表名称",  width = 30,needMerge = true)
    private String sName;

    /**
     * 码表说明
     */
    @Excel(name="码表说明",  width = 30,needMerge = true)
    private String sComment;


    /**
     * 码表状态
     */
    @Excel(name="状态", replace = { "未发布_0","已发布_1", "已停用_2" }, width = 30,needMerge = true)
    private String sStatus;

    /**
     * 更新时间
     */
    @Excel(name="更新时间",  width = 30, databaseFormat = "yyyyMMddHHmmss", format = "yyyy-MM-dd HH:mm:ss", isImportField = "true_st",needMerge = true)
    private Date updateTime;

 
    @ExcelCollection(name = "码表码值")
    private List<StopWatchValueExport> stopWatchValueExportList;
 
}

StopWatchValueExport.java

/**
 * @Role 码值导出实体类
 * @Author 赵平安
 * @GMTCreate   2022年7月22日
 * @Modifier
 * @GMTModified
 * @ModifyContent
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class StopWatchValueExport {

    /**
     * 码值取值
     */
    @Excel(name = "码值取值", width = 30)
    private String cValue;

    /**
     * 码值名称
     */
    @Excel(name = "码值名称", width = 30)
    private String cName;

    /**
     * 码值含义
     */
    @Excel(name = "码值含义", width = 30)
    private String cComment;

}

mytest.java

public class Mytest {
    @Test
    public void haveTitleTest() {

        String PATH = "D:\\idea\\project\\POI_and_easyExcel_study\\easyPOI\\src\\main\\java\\com\\";
        ImportParams params = new ImportParams();
        //设置标题的行数,有标题时一定要有
        params.setTitleRows(1);
        //设置表头的行数
        params.setHeadRows(2);
        String file = PATH+"123.xls";
        List<StopWatchExport> list = ExcelImportUtil.importExcel(
                new File(file),
                StopWatchExport.class, params);
        System.out.println("解析到的数据长度是:" + list.size());
        for (StopWatchExport scoreIssueReqPOJO : list) {
            System.out.println("***********有标题有表头导入的数据是=" + scoreIssueReqPOJO.toString());
        }
    }
}

测试Demo2(导出)

实体类

Employee.java

public class Employee {
 
    /**
     * id
     */
    private String id;
 
    /**
     * 员工姓名
     */
    @Excel(name = "员工姓名",  width = 30,needMerge = true)
    private String name;
 
    /**
     * 员工性别
     */
    @Excel(name = "员工性别", replace = { "男_1", "女_2" }, suffix = "生", isImportField = "true_st",needMerge = true)
    private int sex;
 
    @Excel(name = "开始日期", databaseFormat = "yyyyMMddHHmmss", format = "yyyy-MM-dd", isImportField = "true_st", width = 20,needMerge = true)
    private Date birthday;
 
    @Excel(name = "完工日期", databaseFormat = "yyyyMMddHHmmss", format = "yyyy-MM-dd",needMerge = true)
    private Date registrationDate;
 
    @ExcelCollection(name = "报销金额")
    private List<BaoXiao> baoxiao;
 
}

BaoXiao.java


public class BaoXiao {
    /**
     * 火车票金额
     */
    @Excel(name = "火车票金额", width = 30)
    private String hcpje;
 
    /**
     * 汽车票金额
     */
    @Excel(name = "火车票金额", width = 30)
    private String qcpje;
 
    public BaoXiao(String hcpje, String qcpje) {
        this.hcpje = hcpje;
        this.qcpje = qcpje;
    }
}

Demo

public class Demo {

    String PATH = "D:\\idea\\project\\POI_and_easyExcel_study\\easyPOI\\src\\main\\java\\com\\";
    @Test
    public void fun(){
        try {
            List<BaoXiao> xiaos = new ArrayList<>();
            BaoXiao baoXiao1 = new BaoXiao("100","200");
            BaoXiao baoXiao2 = new BaoXiao("110","210");
            BaoXiao baoXiao3 = new BaoXiao("120","220");
            BaoXiao baoXiao4 = new BaoXiao("130","230");
            xiaos.add(baoXiao1);
            xiaos.add(baoXiao2);
            xiaos.add(baoXiao3);
            xiaos.add(baoXiao4);
 
            List<Employee> list = new ArrayList<>();
            Employee s1 = new Employee("001","张三",1,new Date(),new Date(),xiaos);
            Employee s2 = new Employee("002","李四",2,new Date(),new Date(),xiaos);
            Employee s3 = new Employee("003","王五",1,new Date(),new Date(),xiaos);
            Employee s4 = new Employee("004","赵六",2,new Date(),new Date(),xiaos);
            list.add(s1);
            list.add(s2);
            list.add(s3);
            list.add(s4);

            list.forEach(System.out::println);
            Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams("员工差旅报销金额","学生"),
                    Employee.class, list);
            FileOutputStream outputStream = new FileOutputStream(PATH+"123.xls");
            workbook.write(outputStream);
            outputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

效果图

在这里插入图片描述

实战

实体类(下面有专门对注解的解释)

StopWatchExport.java

/**
 * @Role 码表导出实体类
 * @Author 赵平安
 * @GMTCreate   2022年7月22日
 * @Modifier
 * @GMTModified
 * @ModifyContent
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class StopWatchExport {
    /**
     * 码表编号
     */
    @Excel(name="码表编号",  width = 30,needMerge = true)
    private String sCode;

    /**
     * 码表名称
     */
    @Excel(name="码表名称",  width = 30,needMerge = true)
    private String sName;

    /**
     * 码表说明
     */
    @Excel(name="码表说明",  width = 30,needMerge = true)
    private String sComment;


    /**
     * 码表状态
     */
    @Excel(name="状态", replace = { "未发布_0","已发布_1", "已停用_2" }, width = 30,needMerge = true)
    private String sStatus;

    /**
     * 更新时间
     */
    @Excel(name="更新时间",  width = 30, databaseFormat = "yyyyMMddHHmmss", format = "yyyy-MM-dd HH:mm:ss", isImportField = "true_st",needMerge = true)
    private Date updateTime;

 
    @ExcelCollection(name = "码表码值")
    private List<StopWatchValueExport> stopWatchValueExportList;
 
}

StopWatchValueExport.java

/**
 * @Role 码值导出实体类
 * @Author 赵平安
 * @GMTCreate   2022年7月22日
 * @Modifier
 * @GMTModified
 * @ModifyContent
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class StopWatchValueExport {

    /**
     * 码值取值
     */
    @Excel(name = "码值取值", width = 30)
    private String cValue;

    /**
     * 码值名称
     */
    @Excel(name = "码值名称", width = 30)
    private String cName;

    /**
     * 码值含义
     */
    @Excel(name = "码值含义", width = 30)
    private String cComment;

}

工具类

/**
 * @Title: EasyPoiUtil
 * @ProjectName
 * @Description:
 */
public class EasyPoiUtil {

    /**
     * 导出excel
     *
     * @param list           数据list
     * @param title          标题
     * @param sheetName      sheet名称
     * @param pojoClass      实体的class
     * @param fileName       文件名称
     * @param isCreateHeader 是否创建头
     * @param response       响应
     */
    public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, boolean isCreateHeader, HttpServletResponse response) {
        ExportParams exportParams = new ExportParams(title, sheetName);
        exportParams.setCreateHeadRows(isCreateHeader);
        defaultExport(list, pojoClass, fileName, response, exportParams);

    }

    /**
     * 导出excel
     *
     * @param list      数据list
     * @param title     标题
     * @param sheetName sheet名称
     * @param pojoClass 实体class
     * @param fileName  文件名
     * @param response  响应
     */
    public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, HttpServletResponse response) {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=GBK");
        try {
            response.setHeader("content-disposition",
                    "attachment;filename=" + java.net.URLEncoder.encode(fileName, "GBK")
                            + ";filename*=GBK''" + java.net.URLEncoder.encode(fileName, "GBK"));
        } catch (UnsupportedEncodingException e) {
            // ...
        }
        defaultExport(list, pojoClass, fileName, response, new ExportParams(title, sheetName));
    }

    /**
     * 导出excel
     *
     * @param list     多个Map key title 对应表格Title key entity 对应表格对应实体 key data
     * @param fileName 标题
     * @param response 响应
     */
    public static void exportExcel(List<Map<String, Object>> list, String fileName, HttpServletResponse response) {
        defaultExport(list, fileName, response);
    }

    /**
     * 导出多个excel
     *
     * @param workbooks 多个excel文件 通过ExcelExportUtil.exportExcel往workbooks内放入excel
     * @param fileNames 文件名 每个excel文件的名称顺序必须一致且名称请务必保证不重复
     * @param fileName  压缩包文件名
     * @param response  标题
     */
    public static void exportExcels(List<Workbook> workbooks, List<String> fileNames, String fileName, HttpServletResponse response) {
        try {
            response.setHeader("Content-Disposition",
                "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8") + ".zip");
            OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
            ZipOutputStream zipOut = new ZipOutputStream(toClient);
            for (int i = 0; i < workbooks.size(); i++) {
                ZipEntry entry = new ZipEntry(fileNames.get(i) + ".xls");
                zipOut.putNextEntry(entry);
                workbooks.get(i).write(zipOut);
            }
            zipOut.flush();
            zipOut.close();
        } catch (IOException e) {
            throw new ExcelExportException(e.getMessage());
        }
    }

    private static void defaultExport(List<?> list, Class<?> pojoClass, String fileName, HttpServletResponse response, ExportParams exportParams) {
        Workbook workbook = ExcelExportUtil.exportExcel(exportParams, pojoClass, list);
        if (workbook != null) {
            ;
        }
        downLoadExcel(fileName, response, workbook);
    }

    private static void downLoadExcel(String fileName, HttpServletResponse response, Workbook workbook) {
        try {
            response.setCharacterEncoding("UTF-8");
            response.setHeader("content-Type", "application/vnd.ms-excel");
            response.setHeader("Content-Disposition",
                "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            workbook.write(response.getOutputStream());
        } catch (IOException e) {
            throw new ExcelImportException(e.getMessage());
        }
    }

    private static void defaultExport(List<Map<String, Object>> list, String fileName, HttpServletResponse response) {
        Workbook workbook = ExcelExportUtil.exportExcel(list, ExcelType.HSSF);
        if (workbook != null) {
            ;
        }
        downLoadExcel(fileName, response, workbook);
    }

    public static <T> List<T> importExcel(String filePath, Integer titleRows, Integer headerRows, Class<T> pojoClass) {
        if (StringUtils.isBlank(filePath)) {
            return null;
        }
        ImportParams params = new ImportParams();
        params.setTitleRows(titleRows);
        params.setHeadRows(headerRows);
        List<T> list = null;
        try {
            list = ExcelImportUtil.importExcel(new File(filePath), pojoClass, params);
        } catch (NoSuchElementException e) {
            throw new ExcelImportException("模板不能为空");
        } catch (Exception e) {
            e.printStackTrace();
            throw new ExcelImportException(e.getMessage());
        }
        return list;
    }

    public static <T> List<T> importExcel(MultipartFile file, Integer titleRows, Integer headerRows, Class<T> pojoClass) {
        if (file == null) {
            return null;
        }
        ImportParams params = new ImportParams();
        params.setTitleRows(titleRows);
        params.setHeadRows(headerRows);
        List<T> list = null;
        try {
            list = ExcelImportUtil.importExcel(file.getInputStream(), pojoClass, params);
        } catch (NoSuchElementException e) {
            throw new ExcelImportException("excel文件不能为空");
        } catch (Exception e) {
            throw new ExcelImportException(e.getMessage());
        }
        return list;
    }
}

导入导出操作

controller

    /**
     * @Role 导出码表
     * @Author 赵平安
     * @GMTCreate   2022年7月22日
     * @Modifier
     * @GMTModified
     * @ModifyContent
     */
    @PostMapping("/exportExcel")
    @ApiOperation("导出Excel")
    public void exportExcel(HttpServletResponse response){
        List<StopWatchExport> stopWatchExports = iStopWatchService.StopWatchExport();

        EasyPoiUtil.exportExcel(stopWatchExports,"码表","码表",StopWatchExport.class,"码表.xlsx",response);
    }

    /**
     * @Role 导入码表
     * @Author 赵平安
     * @GMTCreate   2022年7月22日
     * @Modifier
     * @GMTModified
     * @ModifyContent
     */
    @PostMapping("/importExcel")
    @ApiOperation("导入Excel")
    public R importExcel(MultipartFile excelFile){
        try {
            // 参数过滤
            if (excelFile == null){
                return R.Failed("文件不能为空!");
            }else {
                List<StopWatchExport> stopWatchExports = EasyPoiUtil.importExcel(excelFile, 1, 2, StopWatchExport.class);
                iStopWatchService.importExcel(stopWatchExports);
            }
            // 调用服务处理业务
        } catch (Exception e) {
            return R.Failed("未知错误!");
        }
        return R.Success();
    }

service

    /**
     * @Role 码表导出
     * @Author 赵平安
     * @GMTCreate   2022年7月22日
     * @Modifier
     * @GMTModified
     * @ModifyContent
     */
    @Override
    public List<StopWatchExport> StopWatchExport() {
        List<StopWatchExport> stopWatchExports = stopWatchMapper.StopWatchExport();
        return stopWatchExports;
    }

    /**
     * @Role 导入码表
     * @Author 赵平安
     * @GMTCreate   2022年7月22日
     * @Modifier
     * @GMTModified
     * @ModifyContent
     */
    @Override
    public int importExcel(List<StopWatchExport> stopWatchExports) {
        AtomicInteger insert = new AtomicInteger(0);
        stopWatchExports.forEach(stopWatchExport -> {
            List<StopWatchValueExport> stopWatchValueExportList = stopWatchExport.getStopWatchValueExportList();
            System.out.println(stopWatchValueExportList);
            StopWatch stopWatch = new StopWatch();
            stopWatch.setsName(stopWatchExport.getSName());
            stopWatch.setsCode(stopWatchExport.getSCode());
            stopWatch.setsComment(stopWatchExport.getSComment());
            int i = stopWatchMapper.insert(stopWatch);
            insert.set(i);
            StopWatchValue stopWatchValue = new StopWatchValue();
            stopWatchValue.setsId(stopWatch.getsId());
            stopWatchValueExportList.forEach(stopWatchValueExport -> {
                stopWatchValue.setcValue(stopWatchValueExport.getCValue());
                stopWatchValue.setcComment(stopWatchValueExport.getCComment());
                stopWatchValue.setcName(stopWatchValueExport.getCName());
                stopWatchValue.setcId(null);
                System.out.println(stopWatchValue);
                int i2 = stopWatchValueMapper.insert(stopWatchValue);
                insert.set(i2);
            });
        });
        return insert.get();
    }
}

注解

注解介绍

easypoi起因就是Excel的导入导出,最初的模板是实体和Excel的对应,model–row,filed–col 这样利用注解我们可以和容易做到excel到导入导出 经过一段时间发展,现在注解有5个类分别是

@Excel 作用到filed上面,是对Excel一列的一个描述
@ExcelCollection 表示一个集合,主要针对一对多的导出,比如一个老师对应多个科目,科目就可以用集合表示
@ExcelEntity 表示一个继续深入导出的实体,但他没有太多的实际意义,只是告诉系统这个对象里面同样有导出的字段
@ExcelIgnore 和名字一样表示这个字段被忽略跳过这个导导出
@ExcelTarget 这个是作用于最外层的对象,描述这个对象的id,以便支持一个对象可以针对不同导出做出不同处理

@Excel

这个是必须使用的注解,如果需求简单只使用这一个注解也是可以的,涵盖了常用的Excel需求,需要大家熟悉这个功能,主要分为基础,图片处理,时间处理,合并处理几块,name_id是上面讲的id用法,这里就不累言了

属性 类型 默认值 功能
name String null 列名,支持name_id
needMerge boolean fasle 是否需要纵向合并单元格(用于含有list中,单个的单元格,合并list创建的多个row)
orderNum String “0” 列的排序,支持name_id
replace String[] {} 值得替换 导出是{a_id,b_id} 导入反过来
savePath String “upload” 导入文件保存路径,如果是图片可以填写,默认是upload/className/ IconEntity这个类对应的就是upload/Icon/
type int 1 导出类型 1 是文本 2 是图片,3 是函数,10 是数字 默认是文本
width double 10 列宽
height double 10 列高,后期打算统一使用@ExcelTarget的height,这个会被废弃,注意
isStatistics boolean fasle 自动统计数据,在追加一行统计,把所有数据都和输出 这个处理会吞没异常,请注意这一点
isHyperlink boolean false 超链接,如果是需要实现接口返回对象
isImportField boolean true 校验字段,看看这个字段是不是导入的Excel中有,如果没有说明是错误的Excel,读取失败,支持name_id
exportFormat String “” 导出的时间格式,以这个是否为空来判断是否需要格式化日期
importFormat String “” 导入的时间格式,以这个是否为空来判断是否需要格式化日期
format String “” 时间格式,相当于同时设置了exportFormat 和 importFormat
databaseFormat String “yyyyMMddHHmmss” 导出时间设置,如果字段是Date类型则不需要设置 数据库如果是string 类型,这个需要设置这个数据库格式,用以转换时间格式输出
numFormat String “” 数字格式化,参数是Pattern,使用的对象是DecimalFormat
imageType int 1 导出类型 1 从file读取 2 是从数据库中读取 默认是文件 同样导入也是一样的
suffix String “” 文字后缀,如% 90 变成90%
isWrap boolean true 是否换行 即支持\n
mergeRely int[] {} 合并单元格依赖关系,比如第二列合并是基于第一列 则{0}就可以了
mergeVertical boolean fasle 纵向合并内容相同的单元格
fixedIndex int -1 对应excel的列,忽略名字
isColumnHidden boolean false 导出隐藏列

@ExcelTarget

限定一个到处实体的注解,以及一些通用设置,作用于最外面的实体

属性 类型 默认值 功能
value String null 定义ID
height double 10 设置行高
fontSize short 11 设置文字大小

@ExcelEntity

标记是不是导出excel 标记为实体类,一遍是一个内部属性类,标记是否继续穿透,可以自定义内部id

属性 类型 默认值 功能
id String null 定义ID

@ExcelCollection

一对多的集合注解,用以标记集合是否被数据以及集合的整体排序

属性 类型 默认值 功能
id String null 定义ID
name String null 定义集合列名,支持nanm_id
orderNum int 0 排序,支持name_id
type Class<?> ArrayList.class 导入时创建对象使用

@ExcelIgnore

忽略这个属性,多使用需循环引用中

easyExcel讲解(他的官方文档写的很详细,比我写的好得多,如果想学easyExcel只需要看官方文档就行了,也可以跟着我的步骤走,配完一步到位)

easyExcel官方文档

导包

<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>3.1.1</version>
        </dependency>
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.10.14</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.80</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>
</dependencies>

写入测试

1、DemoData.java

@Getter
@Setter
@EqualsAndHashCode
public class DemoData {
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    @ExcelProperty("数字标题")
    private Double doubleData;
    /**
     * 忽略这个字段
     */
    @ExcelIgnore
    private String ignore;
}

2、读操作的时候要写个监听器

// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
@Slf4j
public class DemoDataListener implements ReadListener<DemoData> {

    /**
     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 100;
    /**
     * 缓存的数据
     */
    private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
    /**
     * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
     */
    private DemoDAO demoDAO;

    public DemoDataListener() {
        // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
        demoDAO = new DemoDAO();
    }

    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     *
     * @param demoDAO
     */
    public DemoDataListener(DemoDAO demoDAO) {
        this.demoDAO = demoDAO;
    }

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    // 读取数据会执行invoke方法
    // DemoData 类型
    // AnalysisContext 分析上下文
    @Override
    public void invoke(DemoData data, AnalysisContext context) {
        System.out.println("解析到一条数据:{}"+JSON.toJSONString(data));
        cachedDataList.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (cachedDataList.size() >= BATCH_COUNT) {
            saveData(); //持久化逻辑!
            // 存储完成清理 list
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        log.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        log.info("{}条数据,开始存储数据库!", cachedDataList.size());
        demoDAO.save(cachedDataList);
        log.info("存储数据库成功!");
    }
}

3、持久层(也是读操作需要的)

/**
 * 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。
 **/
public class DemoDAO {
    public void save(List<DemoData> list) {
        // 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入
    }
}

4、Demo.java

public class Demo {
    private List<DemoData> data() {
        List<DemoData> list = ListUtils.newArrayList();
        for (int i = 0; i < 10; i++) {
            DemoData data = new DemoData();
//            data.setString("字符串" + i);
//            data.setDate(new Date());
//            data.setDoubleData(0.56);
            list.add(data);
        }
        return list;
    }

    //根据list  写入excel
    /**
     * 最简单的写
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>
     * 2. 直接写即可
     */

    String PATH = "D:\\idea\\project\\POI_and_easyExcel_study\\02_easyExcel_study\\src\\main\\java\\com\\zhao\\";

    @Test
    public void simpleWrite() {
        // 注意 simpleWrite在数据量不大的情况下可以使用(5000以内,具体也要看实际情况),数据量大参照 重复多次写入

        // 写法1 JDK8+
        // since: 3.0.0-beta1
        String fileName = PATH+"EasyTest.xlsx";
        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        // 如果这里想使用03 则 传入excelType参数即可
        EasyExcel.write(fileName, DemoData.class)
                .sheet("模板")
                .doWrite(() -> {
                    // 分页查询数据
                    return data();
                });

        // 写法2
        fileName = PATH+"EasyTest2.xlsx";
        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        // 如果这里想使用03 则 传入excelType参数即可
        EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());

        // 写法3
        fileName = PATH+"EasyTest3.xlsx";
        // 这里 需要指定写用哪个class去写
        try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {
            WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
            excelWriter.write(data(), writeSheet);
        }
    }
    /**
     * 最简单的读
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>
     * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
     * <p>
     * 3. 直接读即可
     */
    @Test
    public void simpleRead() {
        // 写法1:JDK8+ ,不用额外写一个DemoDataListener
        // since: 3.0.0-beta1
        String fileName = PATH+"EasyTest.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
        // 这里每次会读取100条数据 然后返回过来 直接调用使用数据就行
        EasyExcel.read(fileName, DemoData.class, new PageReadListener<DemoData>(dataList -> {
            for (DemoData demoData : dataList) {
                System.out.println("读取到一条数据{}"+JSON.toJSONString(demoData));
            }
        })).sheet().doRead();

        // 写法2:
        // 匿名内部类 不用额外写一个DemoDataListener
        fileName = PATH+"EasyTest.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
        EasyExcel.read(fileName, DemoData.class, new ReadListener<DemoData>() {
            /**
             * 单次缓存的数据量
             */
            public static final int BATCH_COUNT = 100;
            /**
             *临时存储
             */
            private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

            @Override
            public void invoke(DemoData data, AnalysisContext context) {
                cachedDataList.add(data);
                if (cachedDataList.size() >= BATCH_COUNT) {
                    saveData();
                    // 存储完成清理 list
                    cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
                }
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext context) {
                saveData();
            }

            /**
             * 加上存储数据库
             */
            private void saveData() {
                System.out.println("{}条数据,开始存储数据库!"+cachedDataList.size());
                System.out.println("存储数据库成功!");
            }
        }).sheet().doRead();

        // 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
        // 写法3:
        String fileName = PATH+"EasyTest.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();

        // 写法4
        fileName = PATH+"EasyTest.xlsx";
        // 一个文件一个reader
        try (ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build()) {
            // 构建一个sheet 这里可以指定名字或者no
            ReadSheet readSheet = EasyExcel.readSheet(0).build();
            // 读取一个sheet
            excelReader.read(readSheet);
        }
    }
}

参考:EasyPOI 教程以及完整工具类的使用_HiBoyljw的博客-CSDN博客_easypoi

参考:easypoi一对多导出excel_jattxgt的博客-CSDN博客_easypoi导出excel一对多

参考:【狂神说Java】POI及EasyExcel一小时搞定通俗易懂

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/74744.html

(0)
小半的头像小半

相关推荐

半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!