Springboot使用POI读写excel(详细)

4 阅读6分钟

文章目录 Springboot使用POI读写excel 一、poi简单介绍

  1. workbook工作簿
  2. Sheet表格
  3. Row
  4. Cell 二、Springboot导出excel
  5. 创建controller
  6. 创建service
  7. 处理每行数据
  8. 设置标题行的样式
  9. 创建标题
  10. 到了验证成果的时候啦
  11. 本地导出版本 三、poi读取excel
  12. 解析文件
  13. 解析WorkBook
  14. 处理单元格类型 四、Poi的版本差异 五、Springboot使用EasyExcel读写excel Springboot使用POI读写excel 一、poi简单介绍 官网 poi.apache.org/download.ht…

依赖

org.apache.poi poi 4.1.2 org.apache.poi poi-ooxml 4.1.2 org.apache.poi poi-ooxml-schemas 4.1.2
  1. workbook工作簿 由于Excel存在xls以及xlsx两种格式,所以创建方式也有所不同。

对于xls格式,需要使用HSSFWorkbook来创建对象; 对于xlsx格式,需要使用XSSFWorkbook来创建工作薄; 需要注意HSSFWorkBook与XSSSFWorkbook两个类 都是Workbook接口的实现类。

Workbook orkbook = null; if(fileName.endsWith(".xls")) { orkbook = new HSSFWorkbook(); } else if(fileName.endsWith(".xlsx")) { orkbook = new XSSFWorkbook(); } else { throw new Exception("文件类型错误!"); }

  1. Sheet表格

创建Sheet的时候,存在多种类型。所有我们使用其父类Sheet去处理对应的子类实现。

Sheet sheet = workbook.getSheetAt(sheetNum); //读取

Sheet sheet = workbook.createSheet(sheetName); //创建

  1. Row 作用是定位到特定的行。

sheet.getFirstRowNum() :获取实际第一行 sheet.getPhysicalNumberOfRows():返回有数据的行数,比如n行里有m个空行,返回n-m sheet.getLastRowNum():返回最后一行数据的下标,默认是从0开始 Row row = sheet.getRow(int index); //读取

Row row = sheet.createRow(int index); //创建

  1. Cell 定位到特定的表格

获取到cell上的数据,进行“业务处理”,当然不同的业务逻辑 不同,这里写几个特殊的处理方法。

获取cell对象,下标从0开始 Cell cell = row.getCell(int index); //读取

Cell cell = row.createCell(int index); //创建

关于cell对象的类型

cell.setCellType(Cell.CELL_TYPE_STRING);

获取cell对象的内容

示例

// 第十九列 创建时间 cell = row.getCell(18); if (cell.getCellType().equals(CellType.STRING)) { String value = cell.getStringCellValue(); if (StringUtils.isNotBlank(value)) { resultData.setCreate(LocalDateTime.parse(value, DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"))); } } else if (cell.getCellType().equals(CellType.NUMERIC)) { LocalDateTime localDateTimeCellValue = cell.getLocalDateTimeCellValue(); resultData.setCreate(localDateTimeCellValue); }

二、Springboot导出excel

  1. 创建controller @RestController @RequestMapping("/h5Questionnaire") public class H5QuestionnaireController {

    @Autowired private H5QuestionnaireService h5QuestionnaireService;

    @GetMapping("/download") public void download(HttpServletResponse response) { Workbook workbook = h5QuestionnaireService.getWorkbook();

     OutputStream outputStream = null;
     String fileName = "问卷调查结果" + LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME) + ".xlsx";
     response.reset();
     try {
         response.setContentType("application/vnd.ms-excel;charset=UTF-8");
         response.setCharacterEncoding("UTF-8");
         response.addHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes("gb2312"),"ISO-8859-1"));
    
         outputStream =  new BufferedOutputStream(response.getOutputStream());
         workbook.write(outputStream);
         outputStream.flush();
     } catch (UnsupportedEncodingException e) {
         e.printStackTrace();
     } catch (IOException e) {
         e.printStackTrace();
     } finally {
         try {
             if (outputStream != null) {
                 outputStream.close();
             }
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
    

    } }

  2. 创建service 关于对象,利用反射去做,就不用一列一列的去创建单元格啦,直接循环搞定

注意,对象的字段顺序和标题顺序要对上

@Service @Slf4j public class H5Questionnaire2Service {

// 反射对象的私有字段
private static Field[] declaredFields = H5Questionnaire.class.getDeclaredFields();

@Autowired
private H5Questionnaire2Mapper h5QuestionnaireMapper;

// TODO 以下业务方法

}

//导出 workwoob
public Workbook getWorkbook(){

    log.info("开始创建工作簿...");

    // 创建工作簿
    Workbook workbook = new SXSSFWorkbook(); //生成.xlsx的excel
    // 创建工作表
    Sheet sheet = workbook.createSheet();

    // 构建头单元格样式
    CellStyle cellStyle = buildHeadCellStyle(sheet.getWorkbook());
    cellStyle.setWrapText(true);//自动换行

    log.info("开始创建标题行...");        
    Row head = sheet.createRow(1);//列是从0开始计算的,我这里空了一行
    head.setHeight((short) (8 * 20 * 20)); //1/20th of a point    
    
    //第0列标题,用户id
    Cell cellUid = head.createCell(0);
    cellUid.setCellValue("UserID");
   
    //第1-119列标题
    Map<String, Integer> titleMap = QuestionnaireUtils.getTitleMap();
    int i = 1;
    for (Map.Entry<String, Integer> titie : titleMap.entrySet()) {
        Cell cell = head.createCell(i);
        cell.setCellValue(titie.getKey());
        cell.setCellStyle(cellStyle);
        sheet.setColumnWidth(i, 5 * 2 * 256); //in units of 1/256th of a character width
        i++;
    }
    

	log.info("开始处理数据...");        
    List<H5Questionnaire> list = h5QuestionnaireMapper.selectQuestionnaire();
    int rowNum = 2; //从标题下一行开始

    for (Iterator<H5Questionnaire> it = list.iterator(); it.hasNext(); ) {
        H5Questionnaire data = it.next();
        if (data == null) {
            continue;
        }
        // 构建每行的数据内容
        Row row = sheet.createRow(rowNum++);
        convertDataToRow(h5QuestionnaireVO, row);
    }

    log.info("导出完成");
    return workbook;

}

3. 处理每行数据 这里其实和 EasyExcel差不多,都是对每一行的数据进行处理

private void convertDataToRow(H5Questionnaire data, Row row) {
    int cellNum = 0;
    Cell cell;

    // 第0列 用户id
    cell = row.createCell(cellNum++);
    if (data.getUserId() != null) {
        cell.setCellValue(data.getUserId());
    }

    // 第1-119列数据 利用反射
    Map<String, Integer> titleMap = QuestionnaireUtils.getTitleMap();
    for (Map.Entry<String, Integer> title : titleMap.entrySet()) {
        //log.info("第"+row.getRowNum()+"遍历");

        //通过 当前列 获取对应的对象的 属性值
        Field field = declaredFields[title.getValue() + 2];
        field.setAccessible(true);

        try {
            Object fieldValue = field.get(data);
            cell = row.createCell(cellNum);
            if (fieldValue != null) {
                log.info("第" + row.getRowNum() + "行,第" + cellNum + "列,内容:" + fieldValue);

                // TODO 处理单元格内容
                content = fieldValue.toString();
                cell.setCellValue(content);
            }

        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        cellNum++;
    }


}

大部分情况下,直接 String content = fieldValue.toString(); 就结束了

  1. 设置标题行的样式 private CellStyle buildHeadCellStyle(Workbook workbook) { CellStyle style = workbook.createCellStyle(); //对齐方式设置 左右居中,上下局上 style.setAlignment(HorizontalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.TOP); //边框颜色和宽度设置 style.setBorderBottom(BorderStyle.THIN); style.setBottomBorderColor(IndexedColors.BLACK.getIndex()); // 下边框 style.setBorderLeft(BorderStyle.THIN); style.setLeftBorderColor(IndexedColors.BLACK.getIndex()); // 左边框 style.setBorderRight(BorderStyle.THIN); style.setRightBorderColor(IndexedColors.BLACK.getIndex()); // 右边框 style.setBorderTop(BorderStyle.THIN); style.setTopBorderColor(IndexedColors.BLACK.getIndex()); // 上边框 //设置背景颜色 style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); style.setFillPattern(FillPatternType.SOLID_FOREGROUND); //粗体字设置 Font font = workbook.createFont(); font.setBold(true); style.setFont(font); return style; }

  2. 创建标题 新建工具类,将标题都添加进去 利用对象的反射,可以使用下标,也可以字段名

public class QuestionnaireUtils { private static Map<String, Integer> titleMap = null;

public static Map<String, Integer> getTitleMap() {
    if (titleMap == null) {
        titleMap = new LinkedHashMap<>();
    }
    titleMap.put("1.您目前是否有贷款需求?", 1);
    titleMap.put("2.您打算贷多少钱?(万元)", 2);
    titleMap.put("3.您能承受的最高年化利率是多少?(%)", 3);
    // titleMap.put...
    return titleMap;
}

public static Map<String, String> getUserTable() {
    if (userTable == null) {
        userTable = new LinkedHashMap<>();
    }
    userTable.put("创建时间", "createtime");        
    userTable.put("姓名", "name");
    userTable.put("性别", "idNum");
    userTable.put("学历", "education");
    userTable.put("手机号", "contact");
    userTable.put("出生日期", "birthday");       
    // userTable.put...
    return userTable;
}

}

  1. 到了验证成果的时候啦 大功告成 swagger上的文件名是乱码不要紧,直接黏贴地址到浏览器不乱码就行

  2. 本地导出版本 //导出excel public static void export1(String fileName, Workbook workbook) {

     FileOutputStream fileOutputStream = null;
     try {
         File outFile = new File(fileName);
         if (!outFile.exists()) {
             outFile.createNewFile();
         }
         fileOutputStream = new FileOutputStream(outFile);
         workbook.write(fileOutputStream);
         fileOutputStream.flush();
     } catch (Exception e) {
         log.warn("输出Excel时发生错误,错误原因:" + e.getMessage());
     } finally {
         try {
             if (null != fileOutputStream) {
                 fileOutputStream.close();
             }
             if (null != workbook) {
                 workbook.close();
             }
         } catch (IOException e) {
             log.warn("关闭输出流时发生错误,错误原因:" + e.getMessage());
         }
     }
     
    

    }

三、poi读取excel @Test
public void read() { String fileName = "D:\project\2020.10.28-调查问卷.xlsx";

    creatWorkBook(fileName);
}
  1. 解析文件 private void creatWorkBook(String fileName) {

     log.info("判断文件是否存在...");
     File excelFile = new File(fileName);
     if (!excelFile.exists()) {
         log.warn("指定的Excel文件不存在!");
         return;
     }
    
     log.info("创建 WorkBook ...");
     String fileType = fileName.substring(fileName.lastIndexOf(".") + 1);
     Workbook workbook = null;
     try {
         FileInputStream fileInputStream = new FileInputStream(excelFile);
         if (fileType.equalsIgnoreCase("xls")) {
             workbook = new HSSFWorkbook(fileInputStream); //生成.xls的excel
         } else if (fileType.equalsIgnoreCase("xlsx")) {
             workbook = new XSSFWorkbook(fileInputStream); //生成.xlsx的excel
         } else {
             log.warn("文件格式不对");
             return;
         }
         log.info("开始解析 WorkBook...");
         parseExcel(workbook);
    
     } catch (FileNotFoundException e) {
         e.printStackTrace();
     } catch (IOException e) {
         e.printStackTrace();
     }
    

    }

  2. 解析WorkBook private void parseExcel(Workbook workbook) { for (int sheetNum = 0; sheetNum < workbook.getNumberOfSheets(); sheetNum++) { Sheet sheet = workbook.getSheetAt(sheetNum); // 获取表格 log.info("sheet = {}", sheet.getSheetName());

         // 校验sheet是否合法
         if (sheet == null) {
             continue;
         }
    
        
         Row firstRow = sheet.getRow(sheet.getFirstRowNum()); // 获取第一行,一般是标题
         if (null == firstRow) {
             log.warn("解析Excel失败,在第一行没有读取到任何数据!");
         }
    
         // 解析每一行的数据,构造数据对象
         int rowStart = firstRowNum + 1; //标题下面的数据,数据起始行
         int rowEnd = sheet.getPhysicalNumberOfRows();//获取有记录的行数,即:最后有数据的行是第n行,前面有m行是空行没数据,则返回n-m;
    
         for (int rowNum = rowStart; rowNum < rowEnd; rowNum++) {
             Row row = sheet.getRow(rowNum);
             log.info("第 {} 行", row.getRowNum());
             if (null == row) {
                 continue;
             }
    
             //处理Cell
             for (int cellNum = 1; cellNum < 146; cellNum++) {
                 Cell cell = row.getCell(cellNum);
                 if (cell != null) {
                     String content = convertCellValueToString(cell);
                     log.info("第" + row.getRowNum() + "行,第" + cellNum + "列,内容:" + content);
                     // TODO 处理数据
                 }
             }
    
         }
         
         
     }        
    

    }

  3. 处理单元格类型 private static String convertCellValueToString(Cell cell) { if (cell == null) { return null; } String content = null; try { switch (cell.getCellType()) { case NUMERIC: //数字或者时间 Double doubleValue = cell.getNumericCellValue(); // 格式化科学计数法,取一位整数 DecimalFormat df = new DecimalFormat("0"); content = df.format(doubleValue); break; case STRING: //字符串 content = cell.getStringCellValue(); break; case BOOLEAN: //布尔 Boolean booleanValue = cell.getBooleanCellValue(); content = booleanValue.toString(); break; case BLANK: // 空值 break; case FORMULA: // 公式 content = cell.getCellFormula(); break; case ERROR: // 故障 break; default: break; } } catch (Exception e) { e.printStackTrace(); } return content; }

四、Poi的版本差异 公司项目有点老,用的老版本的poi,然后使用过程中出现以下错误

版本 cellStyle.setAlignment(HSSFCellStyle.ALIGN_CENTER);//水平居中 cellStyle.setVerticalAlignment(XSSFCellStyle.VERTICAL_CENTER);//垂直居中 类好像还要对应

cellStyle.setBorderBottom(CellStyle.BORDER_THIN);//设置边框 cellStyle.setBorderTop(CellStyle.BORDER_THIN);//设置边框 cellStyle.setBorderRight(CellStyle.BORDER_THIN);//设置边框 cellStyle.setBorderLeft(CellStyle.BORDER_THIN);//设置边框 运行项目并下载源码

cellStyle.setAlignment(HorizontalAlignment.CENTER);//居中 cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);//垂直

cellStyle.setBorderBottom(BorderStyle.THIN); //下边框
cellStyle.setBorderTop(BorderStyle.THIN); //上边框
cellStyle.setBorderRight(BorderStyle.THIN); //右边框
cellStyle.setBorderLeft(BorderStyle.THIN); //左边框

五、Springboot使用EasyExcel读写excel 使用EasyExcel读写excel

原文链接:blog.csdn.net/zyd57380383…