Apache POI导入导出excel文件实战

前言

这两天公司项目业务提出需求,要求在前端上传excel文件然后解析展示,因此写篇文章记录一下实现。

技术栈

springboot 2.6.6

1、引入依赖

maven格式:
	<dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>3.14</version>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>3.14</version>
    </dependency>

2、导入代码实现

package com.nanmu.payment.controller;

import com.nanmu.payment.common.MessageConst;
import com.nanmu.payment.entity.Result;
import com.nanmu.payment.pojo.OrderSetting;
import com.nanmu.payment.service.OrderSettingService;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;

/**
 * @author 
 * @description 
 * @date 2022/12/22
 **/
@RestController
@RequestMapping("/ordersetting")
@Slf4j
public class OrderSettingController {
    
    
    /**
     * 上传预约设置的excel文件
     * @param multipartFile
     * @return
     */
    @RequestMapping("/upload")
    public Result upload(@RequestParam("excelFile") MultipartFile multipartFile) {
        log.info("[预约设置-上传]fileName:{},size:{}", multipartFile.getOriginalFilename(), multipartFile.getSize());
        String filename = multipartFile.getOriginalFilename();
        if(StringUtils.isEmpty(filename)){
            return new Result(false,"缺少文件名");
        }
        //1 抽取excel数据 poi

        try (InputStream is = multipartFile.getInputStream();) {
            Workbook workbook = null;
            //1.1构造workbook
            if(filename.endsWith(".xls")){
                // excel 2003
                workbook = new HSSFWorkbook(is);
            }else if(filename.endsWith(".xlsx")){
                // excel 2007
                workbook = new XSSFWorkbook(is);
            }else{
                return new Result(false,"文件格式不正确,请检查重试");
            }

            //1.2遍历取数据
            List<OrderSetting> orderSettings = new ArrayList<>();
            for (Sheet sheet : workbook) {
                // 第0行为表头,直接从第1行取
                for (int i = 1; i <= sheet.getLastRowNum(); i++) {
                    Row row = sheet.getRow(i);
                    Date date = null;
                    int number = 0;
                    try {
                        date = null != row.getCell(0) ? row.getCell(0).getDateCellValue() : null;
                        number = null != row.getCell(1) ? Double.valueOf(row.getCell(1).getNumericCellValue()).intValue() : 0;
                    }catch (IllegalStateException|NumberFormatException e){
                        log.error("",e);
                        return new Result(false,String.format("数据格式错误,%s 第%d行",sheet.getSheetName(),row.getRowNum()+1));
                    }
                    if(null == date ){
                        return new Result(false,String.format("缺少必填数据,%s 第%d行",sheet.getSheetName(),row.getRowNum()+1));
                    }
                    //构造OrderSetting
                    orderSettings.add(new OrderSetting(date,number));
                }
            }
            log.info("[预约设置-上传]解析成功,result:{}",orderSettings);

            //2 调用service层
            //调用service层代码去掉
            return new Result(true,MessageConst.IMPORT_ORDERSETTING_SUCCESS);
        } catch (RuntimeException|IOException e) {
            log.info("",e);
            return new Result(false, MessageConst.IMPORT_ORDERSETTING_FAIL);
        }
    }
    
}

3、导出代码实现

3.1、准备导出文件模板

将导出文件模板放入resources包下面(如下图黄框所示)
在这里插入图片描述
文件内容如下图所示:
在这里插入图片描述

3.2、导出代码实现

package com.nanmu.payment.controller;

import com.nanmu.payment.common.MessageConst;
import com.nanmu.payment.entity.Result;
import com.nanmu.payment.service.MemberService;
import com.nanmu.payment.service.OrderService;
import com.nanmu.payment.service.ReportService;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 数据报告控制器
 */
@RestController
@RequestMapping("/report")
@Slf4j
public class ReportController {

    @Autowired
    private ReportService reportService;

    /**
     * 导出运营数据报表
     *
     * @return
     */
    @GetMapping("exportBusinessReport")
    public Result exportBusinessReport(HttpServletResponse response) {
        log.info("[导出运营数据报表]开始");
        try {
            //1-调用service层获取数据
            Map<String, Object> reportData = reportService.getBusinessReportData();
            log.debug("[导出运营数据报表]rpc rsp:{}", reportData);
            // 取出返回结果
            String reportDate = (String) reportData.get("reportDate");
            // 获取会员相关数据
            Long todayNewMember = (Long) reportData.get("todayNewMember");
            Long thisWeekNewMember = (Long) reportData.get("thisWeekNewMember");
            Long thisMonthNewMember = (Long) reportData.get("thisMonthNewMember");
            Long totalMember = (Long) reportData.get("totalMember");
            // 获取预约相关数据
            Long todayOrderNumber = (Long) reportData.get("todayOrderNumber");
            Long thisWeekOrderNumber = (Long) reportData.get("thisWeekOrderNumber");
            Long thisMonthOrderNumber = (Long) reportData.get("thisMonthOrderNumber");
            // 获取到诊相关数据
            Long todayVisitsNumber = (Long) reportData.get("todayVisitsNumber");
            Long thisWeekVisitsNumber = (Long) reportData.get("thisWeekVisitsNumber");
            Long thisMonthVisitsNumber = (Long) reportData.get("thisMonthVisitsNumber");
            // 获取套餐数据
            List<Map> hotSetmeal = (List<Map>) reportData.get("hotSetmeal");

            //2-读取模板构造workBook
            try (InputStream is = this.getClass().getClassLoader().getResourceAsStream("report_template.xlsx");
                 Workbook workbook = new XSSFWorkbook(is);
                 OutputStream os = response.getOutputStream()) {
                Sheet sheet = workbook.getSheetAt(0);
                //3-填入数据
                //日期
                sheet.getRow(2).getCell(5).setCellValue(reportDate);
                //会员统计数据
                sheet.getRow(4).getCell(5).setCellValue(todayNewMember);
                sheet.getRow(4).getCell(7).setCellValue(totalMember);
                sheet.getRow(5).getCell(5).setCellValue(thisWeekNewMember);
                sheet.getRow(5).getCell(7).setCellValue(thisMonthNewMember);
                //预约到诊数据统计
                sheet.getRow(7).getCell(5).setCellValue(todayOrderNumber);
                sheet.getRow(7).getCell(7).setCellValue(todayVisitsNumber);
                sheet.getRow(8).getCell(5).setCellValue(thisWeekOrderNumber);
                sheet.getRow(8).getCell(7).setCellValue(thisWeekVisitsNumber);
                sheet.getRow(9).getCell(5).setCellValue(thisMonthOrderNumber);
                sheet.getRow(9).getCell(7).setCellValue(thisMonthVisitsNumber);
                //热门套餐
                int rowNum = 12;
                for (Map setMeal : hotSetmeal) {
                    Row row = sheet.getRow(rowNum);
                    row.getCell(4).setCellValue((String) setMeal.get("name"));
                    row.getCell(5).setCellValue((Long) setMeal.get("setmeal_count"));
                    row.getCell(6).setCellValue(((BigDecimal) setMeal.get("proportion")).doubleValue());
                    rowNum++;
                }

                //4-写入网络输出流
                //写入返回流
                response.setContentType("application/vnd.ms-excel");
                response.setHeader("content-Disposition", "attachment;fileName=" + reportDate + "_report.xlsx");
                workbook.write(os);
                return null;
            } catch (IOException e) {
                // 不会出现/无法处理的受检异常,转换为运行时异常
                throw new RuntimeException(e);
            }
        } catch (RuntimeException e) {
            log.error("", e);
            return new Result(false, MessageConst.ACTION_FAIL);
        }

    }

}

4、代码实现解释

  • 导入前端上传的文件不进行存储,直接读取之后将内容存入数据库;
  • 导入支持.xls和.xlsx两种格式excel文件;
  • 导入文件行数不易过大,过大会出现内存泄漏等问题(后面再出关于百万行数据读取、导出的方案);
  • 导出文件使用模板,这样便于写入,同样不支持数据行过多;
  • 导出直接返回.xlsx格式excel文件给前端。

5、常见问题

  • 读取文件失败,提示信息:The supplied data appears to be in the Office 2007+ XML. You are calling the part of POI that deals with OLE2 Office Documents. You need to call a different part of POI to process this data (eg XSSF instead of HSSF)
    分析及解决方案为:经分析发现是HSSFWorkbook不支持读取.xlsx,改为XSSFWorkbook即可。

  • 内存溢出问题,提示信息:java.lang.OutOfMemoryError: Java heap space
    at jdk.internal.reflect.GeneratedConstructorAccessor39.newInstance(Unknown Source) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:na]
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490) ~[na:na]
    at org.apache.xmlbeans.impl.schema.SchemaTypeImpl.createUnattachedNode(SchemaTypeImpl.java:1934) ~[xmlbeans-2.6.0.jar:na]
    at org.apache.xmlbeans.impl.schema.SchemaTypeImpl.createElementType(SchemaTypeImpl.java:1051) ~[xmlbeans-2.6.0.jar:na]
    at org.apache.xmlbeans.impl.values.XmlObjectBase.create_element_user(XmlObjectBase.java:938) ~[xmlbeans-2.6.0.jar:na]
    at org.apache.xmlbeans.impl.store.Xobj.getUser(Xobj.java:1675) ~[xmlbeans-2.6.0.jar:na]
    at org.apache.xmlbeans.impl.store.Xobj.find_all_element_users(Xobj.java:2098) ~[xmlbeans-2.6.0.jar:na]
    at org.openxmlformats.schemas.spreadsheetml.x2006.main.impl.CTRowImpl.getCArray(Unknown Source) ~[poi-ooxml-schemas-3.14.jar:3.14]
    at org.apache.poi.xssf.usermodel.XSSFRow.(XSSFRow.java:73) ~[poi-ooxml-3.14.jar:3.14]
    at org.apache.poi.xssf.usermodel.XSSFSheet.initRows(XSSFSheet.java:215) ~[poi-ooxml-3.14.jar:3.14]
    at org.apache.poi.xssf.usermodel.XSSFSheet.read(XSSFSheet.java:178) ~[poi-ooxml-3.14.jar:3.14]
    at org.apache.poi.xssf.usermodel.XSSFSheet.onDocumentRead(XSSFSheet.java:165) ~[poi-ooxml-3.14.jar:3.14]
    at org.apache.poi.xssf.usermodel.XSSFWorkbook.parseSheet(XSSFWorkbook.java:417) ~[poi-ooxml-3.14.jar:3.14]
    at org.apache.poi.xssf.usermodel.XSSFWorkbook.onDocumentRead(XSSFWorkbook.java:382) ~[poi-ooxml-3.14.jar:3.14]
    at org.apache.poi.POIXMLDocument.load(POIXMLDocument.java:178) ~[poi-ooxml-3.14.jar:3.14]
    at org.apache.poi.xssf.usermodel.XSSFWorkbook.(XSSFWorkbook.java:279) ~[poi-ooxml-3.14.jar:3.14]
    分析及解决方案为:经分析发现是导入、导出数据行数太多,目前建议业务分批导出,后续出百万行数据读取、导出的方案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小白de成长之路

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值