场景

我们需要页面上传一个 excel 文件,并且解析入库,然后对输入值进行校验,最后将校验结果输出给用户。

这里我们暂时只演示基于文件上传的 excel 读取和写入。

excel 读取

maven 依赖

此处为了实现简单,而且考虑到大文件的解析,我们引入 hutool

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-poi</artifactId>
    <version>5.4.0</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>4.1.2</version>
</dependency>
<dependency>
    <groupId>xerces</groupId>
    <artifactId>xercesImpl</artifactId>
    <version>2.12.0</version>
</dependency>

上传 jsp

  • excel.jsp
<!DOCTYPE html>
<%@page contentType="text/html; charset=UTF-8" language="java"%>
<html lang="zh">
<head>
    <title>JSP 实现文件上传和下载</title>
</head>
<body>
<form action="excel/upload" method="post" enctype="multipart/form-data" >
    请选择 EXCEL 文件:
    <input id="file" name="file" type="file" />
    <input type="submit" value="上传"/>
    上传结果:${result}
</form>
</body>
</html>

后端代码

import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.sax.handler.RowHandler;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;

/**
 * @author binbin.hou
 * @since 1.0.0
 */
@Controller
public class ExcelController {

    /**
     * 实现文件上传
     *
     * @param request  请求
     * @param response 响应
     * @return
     */
    @GetMapping("/excel")
    @PostMapping("/excel")
    public String index(HttpServletRequest request,
                        HttpServletResponse response) {
        return "excel";
    }

    /**
     * 实现文件上传
     *
     * @param request  请求
     * @param response 响应
     * @return 页面
     */
    @PostMapping(value = "/excel/upload")
    public String upload(HttpServletRequest request,
                         HttpServletResponse response) throws IOException, ServletException {
        if (request instanceof StandardMultipartHttpServletRequest) {
            StandardMultipartHttpServletRequest sm = (StandardMultipartHttpServletRequest) request;
            MultipartFile multipartFile = sm.getFile("file");
            String filename = multipartFile.getOriginalFilename();
            //2007
            ExcelReader excelReader;
            String uploadDir = request.getServletContext().getRealPath("/WEB-INF/upload/");
            File fileUpload = new File(uploadDir + filename);

            //1. 创建文件
            fileUpload.createNewFile();
            // 写入文件
            try (FileOutputStream fos = new FileOutputStream(fileUpload);
                 BufferedOutputStream bos = new BufferedOutputStream(fos);) {
                bos.write(multipartFile.getBytes());
            } catch (Exception e) {
                e.printStackTrace();
            }

            RowHandler rowHandler = new RowHandler() {
                @Override
                public void handle(int i, long l, List<Object> list) {
                    System.out.println(i+","+l+": " + list);
                }
            };

            if(filename.endsWith("xlsx")) {
                ExcelUtil.read07BySax(fileUpload, 0, rowHandler);
            } else if(filename.endsWith("xls")) {
                ExcelUtil.read03BySax(fileUpload, 0, rowHandler);
            }
            System.out.println("解析完成");
            // 返回结果页面
            request.setAttribute("result", "文件上传成功");

            // 最后删除临时文件
        } else {
            request.setAttribute("result", "文件上传失败");
        }

        return "forward:/file2";
    }

}

这里测试发现,如果直接使用 multipartFile.getInputStream() 处理,内容是空的。

所以通过这种创建临时文件,解析之后,最后删除的解决方案。

测试日志

0,0: [1, A]
0,1: [2, B]
0,2: [3, C]

看的出来,这里的我们可以实现的更加灵活,比如入库,加入列表等等。

RowHandler rowHandler = new RowHandler() {
    @Override
    public void handle(int i, long l, List<Object> list) {
        System.out.println(i+","+l+": " + list);
    }
};

i 代表 sheet 索引

l 代表行数

List<Object> 代表当前行的信息。

excel 写入

写入

写入针对较大的数据量,依然存在可能内存溢出的风险。

对于大量数据输出,采用ExcelWriter容易引起内存溢出,因此有了 BigExcelWriter,使用方法与ExcelWriter完全一致。

实现代码

List<?> row1 = CollUtil.newArrayList("aa", "bb", "cc", "dd", DateUtil.date(), 3.22676575765);
List<?> row2 = CollUtil.newArrayList("aa1", "bb1", "cc1", "dd1", DateUtil.date(), 250.7676);
List<?> row3 = CollUtil.newArrayList("aa2", "bb2", "cc2", "dd2", DateUtil.date(), 0.111);
List<?> row4 = CollUtil.newArrayList("aa3", "bb3", "cc3", "dd3", DateUtil.date(), 35);
List<?> row5 = CollUtil.newArrayList("aa4", "bb4", "cc4", "dd4", DateUtil.date(), 28.00);
List<List<?>> rows = CollUtil.newArrayList(row1, row2, row3, row4, row5);

BigExcelWriter writer = ExcelUtil.getBigWriter("D:\\_github\\jsp-learn\\src\\main\\webapp\\WEB-INF\\upload\\big.xlsx");
// 一次性写出内容,使用默认样式
writer.write(rows);
// 关闭writer,释放内存
writer.close();

参考资料

Excel大数据生成-BigExcelWriter