EasyExcel导入导出

22 篇文章 0 订阅

就两个字,“好用”,分享给大家伙,不管你是新手,还是老鸟,希望对你们有帮助,我个人认为是极好的,阿里出品,必属精品,不吹不黑,个人get到了人家强大的地方了。

简单介绍一下EasyExcel是什么,EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel,对官方文档就是这么阐述的,很简介明了,就是帮助我们干活的,他是基于poi的二次封装,我们只管处理业务,其他的交给EasyExcel,我只是研究了皮毛,他本身有好多api供大家调用,实现复杂组合表头,大数据导入导出,计算函数,多sheet,等等,从我这里入门,然后去扒人家更深的东西

先说导出吧
这是我个人封装的工具类,直接拿去使用,学会了回来留下你的大名就可以了

/**
 * guoyunlong
 * <p>
 * 版本2.1.7 工具类可自定义样式格式等
 * 后续会上传下载功能,大批量导出导出需要使用多线程,和多sheet完成
 **/
@Component
public class ExcelUtil {
    /**
     * 导出 Excel :一个 sheet,带表头.
     *
     * @param response  HttpServletResponse
     * @param data      数据 list,每个元素为一个 BaseRowModel
     * @param fileName  导出的文件名
     * @param sheetName 导入文件的 sheet 名
     * @param model     映射实体类,Excel 模型
     * @throws Exception 异常
     */

    public void writeExcel(HttpServletResponse response, List<? extends Object> data, String fileName, String sheetName,
                           Class model) {
        try {
            // 头的策略
            WriteCellStyle headWriteCellStyle = new WriteCellStyle();
            //设置表头居中对齐
            headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
            // 颜色
            headWriteCellStyle.setFillForegroundColor(IndexedColors.PALE_BLUE.getIndex());
            WriteFont headWriteFont = new WriteFont();
            headWriteFont.setFontHeightInPoints((short) 10);
            // 字体
            headWriteCellStyle.setWriteFont(headWriteFont);
            headWriteCellStyle.setWrapped(true);
            // 内容的策略
            WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
            //设置内容靠中对齐
            contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
            // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现
            HorizontalCellStyleStrategy horizontalCellStyleStrategy =
                    new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
            // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
            EasyExcel.write(getOutputStream(fileName, response), model).excelType(ExcelTypeEnum.XLSX).sheet(sheetName)
                    .registerWriteHandler(horizontalCellStyleStrategy)
                    //最大长度自适应 目前没有对应算法优化 建议注释掉不用 会出bug
                    //                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                    .doWrite(data);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 导出文件时为Writer生成OutputStream.
     *
     * @param fileName 文件名
     * @param response response
     * @return ""
     */
    private OutputStream getOutputStream(String fileName, HttpServletResponse response) throws Exception {
        try {
            fileName = URLEncoder.encode(fileName, "UTF-8");
            response.setContentType("application/json");
            response.setCharacterEncoding("utf8");
            response.setHeader("Content-Disposition", "attachment; filename=" + fileName + ".xlsx");
            response.setHeader("Pragma", "public");
            response.setHeader("Cache-Control", "no-store");
            response.addHeader("Cache-Control", "max-age=0");
            return response.getOutputStream();
        } catch (IOException e) {
            throw new Exception("导出excel表格失败!", e);
        }
    }

}

这是基于实体dto映射实现的

/**
 * @HeadRowHeight() 设置表头高度。
 * @ColumnWidth()设置列宽度。
 * @ExcelProperty(value = "",index = )对应字段名称和位置。
 */
@Data
@HeadRowHeight(30)
public class ParkElectricityMeterDto {

    @ColumnWidth(25)
    @ExcelProperty(value = "监测点", index = 0)
    private String name;

    @ColumnWidth(25)
    @ExcelProperty(value = "当前电量时间", index = 1)
    private String time;

    @ColumnWidth(25)
    @ExcelProperty(value = "有功电能(kWh)", index = 2)
    private String epI;

    @ColumnWidth(25)
    @ExcelProperty(value = "数据采集时间", index = 3)
    private String time1;

    @ColumnWidth(25)
    @ExcelProperty(value = "电压(V)", index = 4)
    private String volP;

    @ColumnWidth(25)
    @ExcelProperty(value = "电流(V)", index = 5)
    private String cur;

    @ColumnWidth(25)
    @ExcelProperty(value = "有功功率(KW)", index = 6)
    private String insP;

    @ColumnWidth(25)
    @ExcelProperty(value = "无功功率(KVar)", index = 7)
    private String insQ;

    @ColumnWidth(25)
    @ExcelProperty(value = "功率因数", index = 8)
    private String pwrF;

    private static final long serialVersionUID = 1L;

}

按照对应的@ExcelProperty注解名字导出的表格也就是对应的名称
下面是我的调用方法

 /**
     * 导出
     *
     * @param resp
     * @param parkId
     * @param ids
     * @param startTime
     * @param endTime
     * @return
     */
    @Override
    public List<ParkElectricityDto> excels(HttpServletResponse resp, Long parkId, String ids, String startTime,
                                           String endTime) {
        List<Map<String, Object>> mapList =
                internetOfThingsApi.getApiCopying(ids, startTime, endTime);
        List<ParkElectricityDto> arrayList = new ArrayList<>();
        mapList.forEach(item -> {
            ParkElectricityDto bo = new ParkElectricityDto();
            bo.setName(item.get("name").toString());
            bo.setStartTime(item.get("first").toString());
            bo.setEndTime(item.get("last").toString());
            bo.setDataValue(item.get("last_first").toString());
            arrayList.add(bo);
        });
        String strName = "excel表名";
        String sheetName = "sheet";
        // 调用导出工具类实现导出
        excelUtil.writeExcel(resp, arrayList, strName, sheetName, ParkElectricityDto.class);
        return arrayList;
    }

到这来我们的最简单的导出也就完成了,基本可以满足百分之80以上的项目组使用了,如果你们有合并表头这种需求,简单的也可以在实体类用包含关系实现,在复杂了就去研究一下官方文档了


导入
其实也很简单,他是实现了一个监听器,这个监听器是不会被spring管理的,很奇怪,我可以改进他这个机制,目前还没有去做,监听器重写了两个方法第一个:invoke()方法,他是用来实现导入数据的第一层解析,对你的集合进行判断,当达到你设定的查询条数的时候他就会进行一次集合内存清理,这样防止几万条数据存在内存,容易OOM,第二个方法:doAfterAllAnalysed(),这个方法是用来做数据处理完成最终确认的地方,保存数据的作用,废话不多说,直接上干货。

/*
 * Copyright (C) 2020 Baidu, Inc. All Rights Reserved.
 */
package com.company.project.configurer;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;
import com.company.project.domain.User;
import com.company.project.service.UserService;

import lombok.extern.slf4j.Slf4j;

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

    private static final Logger LOG = LoggerFactory.getLogger(DemoDataListener.class);

    /**
     * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 3000;
    List<User> list = new ArrayList<User>();
    private UserService userService;

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

    @Override
    public void invoke(User parkElectricityDto, AnalysisContext analysisContext) {
        LOG.info("解析到一条数据:{}", JSON.toJSONString(parkElectricityDto));
        list.add(parkElectricityDto);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (list.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            list.clear();
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        long startTime = System.currentTimeMillis();
        saveData();
        long endTime = System.currentTimeMillis();

        LOG.info("所有数据解析完成!" + (endTime - startTime) + "ms");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        LOG.info("开始存储数据库!", list.size());
        userService.saveData(list);
        LOG.info("存储数据库成功!");
    }
}

仔细阅读上面代码,理解每一层代码的含义,不难,很简单,但是实现复杂数据导入还是在这个基础上去做修改,所以基础不好,房子在高也是样子货,抵不住暴风雨的侵蚀

调用

 /**
     * 导入
     *
     * @param file
     * @return
     * @throws IOException
     */
    @ApiOperation("导入")
    @RequestMapping(value = "/importBasic", method = RequestMethod.POST)
    public Result importBasic(MultipartFile file) throws IOException {
        // DemoDataListener dataListener = new DemoDataListener(userService);
        //  EasyExcel.read(file.getInputStream(), User.class, dataListener).sheet().doRead();
        ExcelReader excelReader = null;
        try {
            excelReader = EasyExcel.read(file.getInputStream(), User.class, new DemoDataListener(userService)).build();
            ReadSheet readSheet = EasyExcel.readSheet(0).build();
            excelReader.read(readSheet);
        } finally {
            if (excelReader != null) {
                // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
                excelReader.finish();
            }
        }
        return ResultGenerator.genSuccessResult();
    }

以上调用实现两种方法,基本一样,我亲自测试了,导入2万条数据,我用时2.4秒,不能说快,基本上达到了很多项目组最高要求了

service业务实现,官方文档给出了一直解释,要想导入快,就必须实现一次insert,也就是说你要在mapping中自己写sql,也有道理的,不过这种也是有弊端的,常年写sql的同学应该知道

  @Override
    public Boolean saveData(List<User> user) {
        User user1 = new User();
        //业务逻辑
        // for (User user2 : user) {
        //  user1.setUsername(user2.getUsername());
        //  user1.setPassword(user2.getPassword());
        //  user1.setNickName(user2.getNickName());
        //  user1.setSex(user2.getSex());
        //  user1.setRegisterDate(user2.getRegisterDate());
        //将解析数据存储数据库
        //  userMapper.insertSelective(user1);
        //   }
        userMapper.plInsert(user);
        return true;
    }

完结撒花,啦啦啦,今天北京下雪了,终于可以睡个懒觉了,希望大家学会了给我留个言,点个赞,虽然很简单的东西,但是实际开发中作用很大,成为架构师你也得走这条路

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

郭优秀的笔记

你的支持就是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值