POI导入导出、EasyExcel批量导入和分页导出

文件导入导出POI、EasyExcel

POI:消耗内存非常大,在线上发生过堆内存溢出OOM;在导出大数据量的记录的时候也会造成堆溢出甚至宕机,如果导入导出数据量小的话还是考虑的,下面简单介绍POI怎么使用

POI导入

首先拿到文件对象,这个对象可以是前端上传的或者从目录读取的,或者OOS里面下载的。
下面是关于POI导入的核心代码,非常的简单。

// 把文件转为Workbook 对象,这样就能任意操作它了
Workbook workbook = new XSSFWorkbook(file.getInputStream());
// 获取第0页的数据
Sheet sheet = workbook.getSheetAt(0);
// 获取第一行
int firstRowNum = sheet.getFirstRowNum();
// 获取最后一行
int lastRowNum = sheet.getLastRowNum();
// 获取第一行数据-即表头
// 如果需要用到表头的话,可以调用Row对象里面的getCell取值
Row head = sheet.getRow(firstRowNum);
// 获取第一列
short firstCellNum = head.getFirstCellNum();
// 获取最后一列
short lastCellNum = head.getLastCellNum();
// 遍历所有数据行-跳过表头
 for (int rowIndex = firstRowNum + 1; rowIndex <= lastRowNum; rowIndex++){
      // 获取数据行
		 Row dataRow = sheet.getRow(rowIndex);
		 // 接下来遍历这一行的数据
		  for (int cellIndex = firstCellNum; cellIndex < lastCellNum; cellIndex++) {
		       // 获取单元格
		  		Cell cell = dataRow.getCell(cellIndex);
		  		// 把单元格数据转为String
		  		System.out.println(cell.toString());
		  }
 }

POI导出

导出功能的话一般会从数据库查出来数据,导入到Excel表中
下面是关于POI导出的核心代码,非常的简单。

   // 创建工作簿
   Workbook wb = new XSSFWorkbook();
   // 创建页
   Sheet sheet = wb.createSheet("Sheet1");
   // 创建表头
   Row tableHeadRow = sheet.createRow(0);
   // 下面就是对表头与行添加数据了
   // 创建表头的单元格
   int headIndex = 0;
   // 这个列数自己控制-可以通过对象的字段数控制,通过反射机制获取对象信息
   for (int i = 0; i < 10; i++) {
        // 创建单元格
        Cell cell = tableHeadRow.createCell(headIndex++);   
          // 设置值-这里的值是自己对象的值
        cell.setCellValue("表头列"+i);
   }
   // 现在表头设置好了,接下来设置数据行
   // 这里从第一行开始,第0行是表头
     for (int i = 1; i < 20; i++) {
         Row dataRow= sheet.createRow(i);
         // 为数据行填充数据,这里的列数和表头的列数一致就行
         for (int i = 0; i < 10; i++) {
          // 创建单元格
          Cell cell = dataRow.createCell(index ++);   
          // 设置值-这里的值是自己对象的值
          cell.setCellValue("数据行的数据"+i);
      }
     }
  
  
   

EasyExcel导入

EasyExcel是阿里巴巴开源的框架,它是对POI进行了封装,解决了POI耗内存的痛点。
使用EasyExcel步骤比较多,但是却能帮助我们更加灵活的开发。
1、实体类上加注解@ExcelProperty(“日期”),日期是表头名

    @ExcelProperty("日期")
    private String prodDate;

2、EasyExcel的监听器,实现ReadListener接口,这里的泛型为实体类

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
public class ImportWRPListenner implements ReadListener<WorkRollPlan> {
    private List<WorkRollPlan> list = new ArrayList<>();

    /**
     * 每读一行触发一次*
     *
     * @param workRollPlan
     * @param analysisContext
     */
    @Override
    public void invoke(WorkRollPlan workRollPlan, AnalysisContext analysisContext) {
        // 每次触发把workRollPlan放进列表
        list.add(workRollPlan);
    }
    /**
     * 所有数据读完之后触发一次*
     *
     * @param analysisContext
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {

    }
}

3、使用

public String uploadExcel(MultipartFile[] files) throws IOException {
        ImportWRPListenner importWRPListenner = new ImportWRPListenner();
        for (int i = 0; i < files.length; i++) {
            MultipartFile file = files[i];
            InputStream inputStream = file.getInputStream();
            EasyExcel.read(inputStream, WorkRollPlan.class, importWRPListenner)
                    .sheet(0) // 读第0页
                    .headRowNumber(1) // 表头占1几行
                    .doRead();
        }
        return "ok";
    }

4、如何入库
在doAfterAllAnalysed方法里把list 入库,入库需要有Service对象,而Listenner是不被Spring管理的,所以说不能通过注入的方式获取Service。
方法一:通过容器获取对象,下面是获取Spring容器的工具类。

import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * spring工具类 方便在非spring管理环境中获取bean
 * 
 * @author ruoyi
 */
@Component
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware 
{
    /** Spring应用上下文环境 */
    private static ConfigurableListableBeanFactory beanFactory;

    private static ApplicationContext applicationContext;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException 
    {
        SpringUtils.beanFactory = beanFactory;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException 
    {
        SpringUtils.applicationContext = applicationContext;
    }

    /**
     * 获取对象
     *
     * @param name
     * @return Object 一个以所给名字注册的bean的实例
     * @throws BeansException
     *
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) throws BeansException
    {
        return (T) beanFactory.getBean(name);
    }

    /**
     * 获取类型为requiredType的对象
     *
     * @param clz
     * @return
     * @throws BeansException
     *
     */
    public static <T> T getBean(Class<T> clz) throws BeansException
    {
        T result = (T) beanFactory.getBean(clz);
        return result;
    }

    /**
     * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
     *
     * @param name
     * @return boolean
     */
    public static boolean containsBean(String name)
    {
        return beanFactory.containsBean(name);
    }

    /**
     * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
     *
     * @param name
     * @return boolean
     * @throws NoSuchBeanDefinitionException
     *
     */
    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException
    {
        return beanFactory.isSingleton(name);
    }

    /**
     * @param name
     * @return Class 注册对象的类型
     * @throws NoSuchBeanDefinitionException
     *
     */
    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException
    {
        return beanFactory.getType(name);
    }

    /**
     * 如果给定的bean名字在bean定义中有别名,则返回这些别名
     *
     * @param name
     * @return
     * @throws NoSuchBeanDefinitionException
     *
     */
    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException
    {
        return beanFactory.getAliases(name);
    }

    /**
     * 获取aop代理对象
     * 
     * @param invoker
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T getAopProxy(T invoker)
    {
        return (T) AopContext.currentProxy();
    }

    /**
     * 获取当前的环境配置,无配置返回null
     *
     * @return 当前的环境配置
     */
    public static String[] getActiveProfiles()
    {
        return applicationContext.getEnvironment().getActiveProfiles();
    }

    /**
     * 获取当前的环境配置,当有多个环境配置时,只获取第一个
     *
     * @return 当前的环境配置
     */
    public static String getActiveProfile()
    {
        final String[] activeProfiles = getActiveProfiles();
        return "StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null";
    }

    /**
     * 获取配置文件中的值
     *
     * @param key 配置文件的key
     * @return 当前的配置文件的值
     *
     */
    public static String getRequiredProperty(String key)
    {
        return applicationContext.getEnvironment().getRequiredProperty(key);
    }
}

通过Spring获取Service

 private WorkRollPlanService workRollPlanService = SpringUtils.getBean(WorkRollPlanService.class);

方法2:构建监听器的时候把Service传进来

 private WorkRollPlanService workRollPlanService;

    public ImportWRPListenner(WorkRollPlanService workRollPlanService) {
        this.workRollPlanService = workRollPlanService;
    }

在Service里面使用

ImportWRPListenner importWRPListenner = new ImportWRPListenner(this);

拿到Service之后在监听器的doAfterAllAnalysed方法入库就OK了。

5、如何导入指定条数就插入数据库。

public class ImportWRPListenner implements ReadListener<WorkRollPlan> {
	// 计数器
    public static int count = 0;

    private WorkRollPlanService workRollPlanService;

    public ImportWRPListenner(WorkRollPlanService workRollPlanService) {
        this.workRollPlanService = workRollPlanService;
    }

    private List<WorkRollPlan> list = new ArrayList<>(2000);

    /**
     * 每读一行触发一次*
     *
     * @param workRollPlan
     * @param analysisContext
     */
    @Override
    public void invoke(WorkRollPlan workRollPlan, AnalysisContext analysisContext) {
        // 每次触发把workRollPlan放进列表
        list.add(workRollPlan);
        count++;
        // 一旦计数器到达2000或者2000的倍数时,插入数据库,并清空list,释放内存
        if (count % 2000 == 0) {
            workRollPlanService.preUpload(list);
            list.clear();
        }
    }

    /**
     * 所有数据读完之后触发一次*
     *
     * @param analysisContext
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        // 最后不满2000条的数据在这里插入数据库
        workRollPlanService.preUpload(list);
    }
}

EasyExcel导出

public void WorkRollPlansToFileStream(HttpServletResponse response, List<WorkRollPlan> workRollPlans) throws IOException {
		response.setHeader("Content-disposition", "attachment;");
    OutputStream output = response.getOutputStream();
		 EasyExcel.write(output)
                .head(WorkRollPlan.class)
                .excelType(ExcelTypeEnum.XLSX)
                .sheet("Sheet1")
                .doWrite(workRollPlans);
        output.close();
}

看下效果
在这里插入图片描述
把id也导出了,这是我们不需要的。
需要在实体类上加注解

@ExcelIgnoreUnannotated

EasyExcel结合Mybatis plus的分页功能进行分页导出

工具类

import cn.hutool.core.collection.CollUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.Builder;
import lombok.Data;
import org.springframework.http.HttpHeaders;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

public class EasyExcelUtil {
    @Data
    @Builder
    public static class ExcelParam {
        /**
         * 查询 Mapper
         */
        private BaseMapper baseMapper;

        /**
         * Lambda查詢方式
         */
        private LambdaQueryWrapper lambdaQueryWrapper;

        /**
         * 页码,默认从1开始
         */
        private Integer pageNo = 1;

        /**
         * 分页条数,,默认每个sheet 1000 条数据
         */
        private Integer pageSize = 1000;

        /**
         * 用于存放查询到的結果,让Excel生成
         */
        private Class<?> respClazz;

        /**
         * 生成的Excel 名称,不加后缀
         */
        private String fileName;

        /**
         * Excel sheet名称
         */
        private String sheetName;
    }

    public static void exportExcel(ExcelParam excelParam, HttpServletResponse response) {
        response.setContentType("application/vnd.ms-excel;charset=utf-8");
        response.setCharacterEncoding("utf-8");
        response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION);
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + excelParam.getFileName() + ".xlsx");
        try (
                ServletOutputStream outputStream = response.getOutputStream();
                ExcelWriter excelWriter = EasyExcel.write(outputStream, excelParam.getRespClazz()).build();
        ) {
            Page page = new Page(excelParam.getPageNo(), excelParam.getPageSize());
            page = (Page) excelParam.getBaseMapper().selectPage(page, excelParam.getLambdaQueryWrapper());
            /** 构建 */
            WriteSheet writeSheet1 = EasyExcel.writerSheet(1, excelParam.getSheetName() + "第" + excelParam.getPageNo() + "页").build();
            /** 获取总数 */
            Long totalPage = page.getPages();
            List records = page.getRecords();
            /** 写入内容 */
            excelWriter.write(records, writeSheet1);
            writeSheet1 = null; // GC
            // 若为空表
            if (CollUtil.isEmpty(page.getRecords())) {
                /** 生成完毕 */
                excelWriter.finish();
                /** 立即刷回 */
                outputStream.flush();
                return;
            }
            for (int i = excelParam.pageNo + 1, index = 2; i <= totalPage; i++, index++) {
                /** 清空*/
                records.clear();
                WriteSheet writeSheet = EasyExcel.writerSheet(index, excelParam.getSheetName() + "第" + i + "页").build();
                page.setCurrent(i);
                /** 新的查询 */
                page = (Page) excelParam.getBaseMapper().selectPage(page, excelParam.getLambdaQueryWrapper());
                records = page.getRecords();
                /** 输入内容內容 */
                excelWriter.write(records, writeSheet);
            }
            /** 生成完毕 */
            excelWriter.finish();
            /** 立即刷回 */
            outputStream.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

使用

EasyExcelUtil.ExcelParam p = EasyExcelUtil.ExcelParam.builder()
                .baseMapper(workRollPlanService.getBaseMapper())           // 传Service类的BaseMapper进去
                .lambdaQueryWrapper(new LambdaQueryWrapper<WorkRollPlan>() // 构建查询条件
                        .eq(true, WorkRollPlan::getIsDeleted, 0)
                )
                .pageNo(1)                     // 数据从第一行开始,如果表头只有一行就是1,表头有两行的话就写2
                .respClazz(WorkRollPlan.class) // 这里写实体类
                .pageSize(1000)                // Excel每页数据大小
                .fileName("test")              // 文件名
                .sheetName("测试sheet")        // 页名
                .build();

        EasyExcelUtil.exportExcel(p, response);

你可能感兴趣的:(java,开发语言)