使用POI和EasyExcel实现数据的导入导出功能


前言

功能:将数据库中的数据导出到Excel,将Excel中的数据导入导数据库。
提示:原始POI的方式比较繁琐,不想看的可以跳过,直接看方式二,使用阿里巴巴帮我们封装好POI的easyExcel

一、03版Excel和07版Excel的区别

具体两者有哪些区别,请参考https://wenku.baidu.com/view/6c592a97f524ccbff1218477.html
这里只关注比较重要的两点:
03版本的Excel是xls文件,07版本的Excel是xlsx文件
03版最多可以放65536行数据,而07版可以存放100多万行。
数据量的大小决定着采用哪种方式进行数据的导入导出。POI提供了以下三种WorkBook实现类:
(1)HSSF
优点:是过程中写入缓存、不操作磁盘,最后一次性写入磁盘,速度快
缺点:最多只能处理65536行数据,操作xls类型(03)
(2)XSSF
优点:可以写较大的数据量,如20万行数据(比起HSFF多了不少)
缺点:写数据时速度非常慢,非常耗内存,也会发生内存溢出,如100万行数据的时候
(3)SXSSF
优点:XSSF的升级版,可以写非常大的数据量,如100万条甚至更多条,写数据速度快,占用更少的内存。
需要注意的就是,过程中会产生临时文件,需要清理。
好了,读到这里,你只需要知道什么类型的Excel选用什么类型的WorkBook实现类即可

二、方式一:POI

首先第一步就是要导入所需要的依赖
其中joda.time和Junit依赖非必须导入,只是便于我们接下来的测试

<!-- xls(03)-->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>4.1.1</version>
</dependency>
<!-- xls(07)-->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>4.1.1</version>
</dependency>
<!-- 日期格式化工具-->
<dependency>
    <groupId>joda-time</groupId>
    <artifactId>joda-time</artifactId>
    <version>2.10.1</version>
</dependency>
<!-- test-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

1.导出

1.1 导出单行数据

public class ExcelWriteTest {
    
    
    String PATH = "D:\\yzh1024Project\\car_system\\";
    @Test
    public void test03() throws Exception {
    
    
        //1、创建一个工作簿
        Workbook workbook = new HSSFWorkbook();
        //2、创建一个工作表
        Sheet sheet = workbook.createSheet("统计表");
        //3、创建一个行
        Row row1 = sheet.createRow(0);
        //4、创建单元格
        Cell cell00 = row1.createCell(0);
        cell00.setCellValue("hahah");
        Cell cell01 = row1.createCell(1);
        cell01.setCellValue(666);

        Row row2 = sheet.createRow(1);
        //再创建一行
        Cell cell10 = row2.createCell(0);
        cell10.setCellValue("时间:");
        Cell cell11 = row2.createCell(1);
        String time = new DateTime().toString("yyyy-MM-dd HH:mm:ss");
        cell11.setCellValue(time);

        //生成一张表
        FileOutputStream fileOutputStream = new FileOutputStream(PATH + "统计表.xls");
        //输出
        workbook.write(fileOutputStream);
        //关闭流
        fileOutputStream.close();
        System.out.println("文件生成完毕");
    }
}

上面是03版的,改为07版只需要改两个地方(对象和文件后缀)
在这里插入图片描述
1.2 导出多行数据

public class ExcelWriteTest {
    
    
    String PATH = "D:\\yzh1024Project\\car_system\\";
    @Test
    public void test07() throws Exception {
    
    
        long begin = System.currentTimeMillis();
        //1、创建一个工作簿(使用XSSFWorkbook的升级版SXSSFWorkbook,可加快速度)
        Workbook workbook = new SXSSFWorkbook();
        //2、创建一个工作表
        Sheet sheet = workbook.createSheet("统计表");

        for (int i = 0; i < 100000; i++) {
    
    
            Row row = sheet.createRow(i);
            for (int j = 0; j < 10; j++) {
    
    
                Cell cell = row.createCell(j);
                cell.setCellValue(i);
            }
        }
        //生成一张表
        FileOutputStream fileOutputStream = new FileOutputStream(PATH + "统计表.xlsx");
        //输出
        workbook.write(fileOutputStream);
        //关闭流
        fileOutputStream.close();
        //清除临时文件
        ((SXSSFWorkbook)workbook).dispose();
        System.out.println("文件生成完毕");
        long end = System.currentTimeMillis();
        System.out.println((double)(end-begin)/1000);
    }
}

2.导入

2.1 导入单行数据

public class ExcelReadTest {
    
    
    String PATH = "D:\\yzh1024Project\\car_system\\";
    @Test
    public void test07() throws Exception {
    
    
        //获取文件流
        FileInputStream inputStream = new FileInputStream(PATH + "统计表.xls");
        //1、创建一个工作簿。使用Excel能操作的它都能操作
        Workbook workbook = new HSSFWorkbook(inputStream);
        //2、获取表
        Sheet sheet = workbook.getSheetAt(0);
        //3、获取行
        Row row = sheet.getRow(0);
        //4、获取单元格
        Cell cell = row.getCell(0);

        //注意:这里的类型要和单元格里的数据类型一致
        //getStringCellValue获取字符串类型
        System.out.println(cell.getStringCellValue());
        inputStream.close();
    }
}

上面是03版的,改为07版只需要改两个地方(对象和文件后缀)
在这里插入图片描述
2.2 导入多行数据(注意处理不同的数据类型)
测试将如下表格中的数据导入
在这里插入图片描述
还要用到下面的工具类

public class ExcelReadUtils {
    
    

    /**
     * Excel的读取(处理不同类型的数据)
     * @param inputStream
     * @throws Exception
     */
    public static void ExcelRead03(FileInputStream inputStream) throws Exception {
    
    
        //创建工作簿
        Workbook workbook = new HSSFWorkbook(inputStream);
        //获取表对象
        Sheet sheet = workbook.getSheetAt(0);
        //获取第一行
        Row rowTitle = sheet.getRow(0);

        //获取标题内容
        if (rowTitle != null) {
    
    
            //获取该行的列数
            int cellCount = rowTitle.getPhysicalNumberOfCells();
            for (int i = 0; i < cellCount; i++) {
    
    
                //获取单元格
                Cell cell = rowTitle.getCell(i);
                if (cell != null) {
    
    
                    CellType cellType = cell.getCellType();
                    String stringCellValue = cell.getStringCellValue();
                    System.out.print(stringCellValue + "\t");
                }
            }
            System.out.println();
        }

        //获取表中的内容
        //获取行数
        int rowCount = sheet.getPhysicalNumberOfRows();
        for (int j = 1; j < rowCount; j++) {
    
    
            Row row = sheet.getRow(j);
            if (row != null) {
    
    
                //读取行中的列
                int cellCount = row.getPhysicalNumberOfCells();
                for (int i = 0; i < cellCount; i++) {
    
    
                    Cell cell = row.getCell(i);
                    //匹配列的数据类型
                    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:
                                //先判断是日期还是浮点数
                                if (HSSFDateUtil.isCellDateFormatted(cell)) {
    
    
                                    System.out.print("[date]");
                                    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;
                            default:
                        }
                        System.out.println(cellValue);
                    }
                }
            }
        }
        inputStream.close();
    }
}

注意:在该工具类中,我仅仅只是将单元格里读取到的数据System.out.println(cellValue);,用的时候,将这里改为数据库存储即可,按需修改
测试导入功能:

public class Main {
    
    
    public static void main(String[] args) throws Exception {
    
    
        //表路径
        String PATH = "D:\\yzh1024Project\\car_system\\";
        //获取文件流
        FileInputStream inputStream = new FileInputStream(PATH + "统计表.xls");
        ExcelReadUtils.ExcelRead03(inputStream);
    }
}

3.Excel中的公式

在这里插入图片描述

public class Main {
    
    
    public static void main(String[] args) throws Exception {
    
    
        String PATH = "D:\\yzh1024Project\\car_system\\";
        //获取文件流
        FileInputStream inputStream = new FileInputStream(PATH + "统计表.xls");
        //找到带公式的单元格
        Workbook workbook = new HSSFWorkbook(inputStream);
        Sheet sheet = workbook.getSheetAt(0);
        Row row = sheet.getRow(4);
        Cell cell = row.getCell(1);
        //拿到计算公式
        FormulaEvaluator formulaEvaluator = new HSSFFormulaEvaluator((HSSFWorkbook) workbook);
        //输出单元格的内容
        CellType cellType = cell.getCellType();
        switch (cellType){
    
    
            case FORMULA:
                //得到公式
                String formula = cell.getCellFormula();
                System.out.println(formula);
                //计算结果
                CellValue evaluate = formulaEvaluator.evaluate(cell);
                String cellValue = evaluate.formatAsString();
                System.out.println(cellValue);
                break;
            default:
        }
        System.out.println(cell);
    }
}

三、方式二:easyExcel

简介:快速、简单避免OOM的java处理Excel工具 github地址:https://github.com/alibaba/easyexcel
第一步导入依赖

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.2.7</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.75</version>
        </dependency>

1.导出

第一步:编写一个实体类(会根据实体类自动生成表)

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

第二步:编写测试代码

public class TestEasyExcel {
    
    
    /**
     * 导出的地址
     */
    String PATH = "D:\\yzh1024Project\\car_system\\";

    /**
     * 编写data方法(通用数据方法,后面不会重写)
     * 测试数据
     * @return
     */
    private List<DemoData> data() {
    
    
        List<DemoData> list = new ArrayList<DemoData>();
        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);
        }
        //根据list,写入Excel
        return list;
    }

    /**
     * 最简单的写
     * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>2. 直接写即可
     */
    @Test
    public void simpleWrite() {
    
    
        String fileName = PATH + "demo.xlsx";
        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        // 如果这里想使用03 则 传入excelType参数即可
        EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
    }
}

注意:这里的list只是测试数据,真实开发场景中,将这个list改为数据库中查出来的数据即可
这个Excel文件就生成了
在这里插入图片描述

2.导入

第一步:编写一个实体类(就用刚才的DemoData,这里就不再重复写了)
第二步:创建DAO

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

第三步:编写监听器

// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
public class DemoDataListener extends AnalysisEventListener<DemoData> {
    
    
    private static final Logger LOGGER = LoggerFactory.getLogger(DemoDataListener.class);
    /**
     * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 5;
    List<DemoData> list = new ArrayList<DemoData>();
    /**
     * 假设这个是一个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;
    }
    /**
     * 这个每一条数据解析都会来调用
     * DemoData 类型
     * AnalysisContext 分析上下文
     * @param data
     *            one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(DemoData data, AnalysisContext context) {
    
    
        System.out.println("解析到一条数据:{"+JSON.toJSONString(data)+"}");
        LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));
        list.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (list.size() >= BATCH_COUNT) {
    
    
            //持久化操作
            saveData();
            // 存储完成清理 list
            list.clear();
        }
    }
    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
    
    
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        LOGGER.info("所有数据解析完成!");
    }
    /**
     * 加上存储数据库
     */
    private void saveData() {
    
    
        LOGGER.info("{}条数据,开始存储数据库!", list.size());
        demoDAO.save(list);
        LOGGER.info("存储数据库成功!");
    }
}

第四步:编写测试代码

public class TestEasyExcel {
    
    
    /**
     * 导出的地址
     */
    String PATH = "D:\\yzh1024Project\\car_system\\";

    /**
     * 最简单的读
     * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
     * <p>3. 直接读即可
     */
    @Test
    public void simpleRead() {
    
    
        // 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
        // 这里就用刚才生成的demo.xlsx
        String fileName = PATH + File.separator + "demo.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();

    }
}

测试结果:
在这里插入图片描述
怎么样,easyExcel是不是很简单很方便。
这里只是easyExcel最简单的入门程序,更多操作请参考官方文档https://www.yuque.com/easyexcel/doc/read
读和写的固定套路:
写入(导出):固定类格式进行写入
读取(写入):根据监听器设置的规则进行读取

当你真心想要做一件事的时候,整个宇宙都会帮助你,加油!

猜你喜欢

转载自blog.csdn.net/qq_42805101/article/details/115943304