使用 EasyExcel 读取和下载 excel 文件

前言

EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单,节省内存著称,EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。

EasyExcel采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)。

构建工具类
@Data
public class User{

    @ExcelProperty(value = "名称", index = 0)
    @ColumnWidth(value = 15)
    private String name;//名称

    @ExcelProperty(value = "年龄", index = 1)
    @ColumnWidth(value = 15)
    private String age;//患者姓名

    @ExcelProperty(value = "性别", index = 2)
    @ColumnWidth(value = 15)
    private String sex;//患者id

    @ExcelProperty(value = "创建时间", index = 4)
    @ColumnWidth(value = 15)
    private String confirmDate;//创建时间
}
读取excel文件

读取excle文件可以分为两种方式,一种是异步读取,另一种是同步读取

异步

异步读取首先要构建监听器继承AnalysisEventListener

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;

import java.util.ArrayList;
import java.util.List;

public class ExcelListener<T> extends AnalysisEventListener<T> {

    private List<T> list=new ArrayList<>();

    //读取每一行数据执行一次
    @Override
    public void invoke(T t, AnalysisContext analysisContext) {
        list.add(t);
    }
	//读取表头内容
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        System.out.println("表头:"+headMap);
    }
	//数据全部读取完毕
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
    	save(list);
        System.out.println("所有数据解析完毕.................");
    }
    public void save(List<T> list){
		//逻辑代码
	}
}

读取代码

public class ExcelUtils {
/**
     * 读取excel文件
     * @param file 文件
     * @return List
     */
    public static void readExcel(File file){
        InputStream ins=null;
        try {
            ins=new FileInputStream(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        //sheet 设置处理的工作簿 headRowNumber 设置从excel第几行开始读取
       EasyExcel.read(ins, User.class ,new ExcelListener<User>()).sheet().headRowNumber(1).doRead();//第0行一般是表头,从第1行开始读取
    }
}
public class Test{
	public static void main(String[] args) {
		ExcelUtils readExcel(new File("D:\\test.xlsx"),User.class);
	}
}
同步

同步读取可以不需要上面的监听器了(不过如果需要验证表头的话,还是可以加上监听器来验证表头信息)
其实也就是 一个使用 doRead() 另一个使用 doReadSync()

public class ExcelUtils {
    /**
     * 读取excel文件
     * @param file 文件
     * @return List
     */
    public static List readExcel(File file,Class clazz){
        InputStream ins=null;
        try {
            ins=new FileInputStream(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        //sheet 设置处理的工作簿 headRowNumber 设置从excel第几行开始读取
        return EasyExcel.read(ins).head(clazz).sheet().headRowNumber(1).doReadSync();//第0行一般是表头,从第1行开始读取
    }

一些读取数据可能会碰到的小问题

1.在exlcel中有明明有数据,但是读取进来后对象属性全是空

有可能是使用了 注解 @Accessors(chain = true) 这个注解会和 EasyExcel 有冲突,导致数据无法读取进来,一些高版本好像已经修复了此BUG

2.在读取日期形式的数据时,可能会造成精度丢失 比如:

在这里插入图片描述
这样的 yyyy-MM-dd HH:mm:ss 数据读取进来后可能会 变成 yyyy-MM-dd HH:mm

也就是这样
在这里插入图片描述
这种情况可以在实体工具类中增加一个注解,来让EasyExcel 知道读取日期的格式

	@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
    @ExcelProperty(value = "创建时间", index = 4)
    @ColumnWidth(value = 15)
    private String confirmDate;//创建时间

这样读取进来的日期就会正常了

3.在目前的 easyExcel 中有一些 BUG ,如果 excel 第一行没有内容,是不会触发 invokeHeadMap 的方法的
也就是说如果你传入的 excel 文件第一行没有内容,那么你写在 invokeHeadMap 方法中的表头验证方法是不会被触发的

目前有一个办法是在 invoke 中写一些代码进行判断 如下:

 	private boolean isModel=true;
	 @Override
    public void invoke(Object o, AnalysisContext analysisContext) {
        //非正常表格式验证,第一行没数据
        if(isModel){
            throw new SystemException("请使用规定模板查询");
        }
    }
    
    @Override
    public void invokeHeadMap(Map headMap, AnalysisContext context) {
        if(isModel){
            isModel=false;
        }
        ..........//其他验证表头代码
	}
导出excel文件
向浏览器输出excel文件
public class ExcelUtils {
	/**
     * 像浏览器输出excel文件
     * @param response  HttpServletResponse
     * @param data  输出的数据
     * @param fileName 输出的文件名称 excel的名称
     * @param sheetName  输出的excel的sheet的名称 也就是页的名称
     * @param clazz  输出数据的模板
     */
    public static void  writeExcel(HttpServletResponse response, List<? extends Object> data, String fileName, String sheetName, Class clazz){
        //表头样式
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        //设置表头居中对齐
        headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        //内容样式
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        //设置内容靠左对齐
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);
        HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
        try {
            EasyExcel.write(getOutputStream(fileName, response), clazz).excelType(ExcelTypeEnum.XLSX).sheet(sheetName).registerWriteHandler(horizontalCellStyleStrategy).doWrite(data);
        } catch (Exception e) {
            throw new SystemException("输出excel文件失败", e);
        }

    }

    private static OutputStream getOutputStream(String fileName, HttpServletResponse response) throws Exception {
        fileName = URLEncoder.encode(fileName, "UTF-8");
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf8");
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
        return response.getOutputStream();
    }
}
@GetMapping("/test/downloadExcel")
public static void test(){
	List<User> userList=new Arraylist<>();
	//省略添加数据代码
	try {
         ExcelUtil.writeExcel(response, userList, fileName, sheetName, UserExcel.class);
    } catch (Exception e) {
           e.printStackTrace();
    }
}
读取模版,向指定位置输出excel文件
public class ExcelUtils {
    /*
     * @Author: pang.jialei
     * @Description: 读取excle模板,将数据输出到模板
     * @Date: 2021/6/3 15:04
     * @Param: templateInputStream: 模板所在的地址
     * @param tempFileName: 生成的excel文件名称
     * @param downloadDir: 输出excel文件的地址
     * @param parms: list表格数据
     * @param mapData: 其他参数
     **/
    public static void invoke(InputStream templateInputStream, String tempFileName, String downloadDir, List<?> parms, Map<String, Object> mapData) {
        File file = new File(downloadDir);
        if (!file.exists()) {
            file.mkdirs();
        }
        String tempFile = downloadDir + File.separator + tempFileName;

        ExcelWriter excelWriter = EasyExcel.write(tempFile).withTemplate(templateInputStream).build();
        WriteSheet writeSheet = EasyExcel.writerSheet().build();
        // 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。
        // forceNewRow 如果设置了true,有个缺点 就是他会把所有的数据都放到内存了,所以慎用
        // 简单的说 如果你的模板有list,且list不是最后一行,下面还有数据需要填充 就必须设置 forceNewRow=true 但是这个就会把所有数据放到内存 会很耗内存
        // 如果数据量大 list不是最后一行 参照下一个
        if (parms != null) {
            FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
            excelWriter.fill(parms, fillConfig, writeSheet);
        }
        if (mapData != null) {
            excelWriter.fill(mapData, writeSheet);
        }
        excelWriter.finish();
    }
读取模版,向指定位置输出多sheet excel文件

    /*
     * @Author: pang.jialei
     * @Description: 读取excle模板,将数据输出到多sheet模板
     * @Date: 2021/6/3 15:04
     * @Param: templateInputStream: 模板所在的地址
     * @param tempFileName: 生成的excel文件名称
     * @param downloadDir: 输出excel文件的地址
     * @param listMapData:  的表格数据
     * @param sheetNames: sheet名称
     * @param mapData: > 其他参数
     **/
    public static void invokeListSheet(InputStream templateInputStream, String tempFileName, String downloadDir, Map<String,List<?>> listMapData, List<String> sheetNames, Map<String,Map<String, Object>> mapData) {
        File file = new File(downloadDir);
        if (!file.exists()) {
            file.mkdirs();
        }
        String tempFile = downloadDir + File.separator + tempFileName;
        ExcelWriter excelWriter = EasyExcel.write(tempFile).withTemplate(templateInputStream).build();
        for (String sheet:sheetNames) {
            WriteSheet writeSheet = EasyExcel.writerSheet(sheet).build();
            // 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。
            // forceNewRow 如果设置了true,有个缺点 就是他会把所有的数据都放到内存了,所以慎用
            // 简单的说 如果你的模板有list,且list不是最后一行,下面还有数据需要填充 就必须设置 forceNewRow=true 但是这个就会把所有数据放到内存 会很耗内存
            // 如果数据量大 list不是最后一行 参照下一个
            List parms= listMapData.get(sheet);
            if (parms != null) {
                FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
                excelWriter.fill(parms, fillConfig, writeSheet);
            }
            if (mapData != null) {
                excelWriter.fill(mapData.get(sheet), writeSheet);
            }
        }
        excelWriter.finish();
    }

你可能感兴趣的:(web,Spring,Spring,boot,java,excel,poi,spring)