文件导入导出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);