POI报表导入导出
POI报表导入
搭建环境
<!-- POI2003 --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>4.0.1</version> </dependency> <!--高版本poi2007--> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>4.0.1</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml-schemas</artifactId> <version>4.0.1</version> </dependency>
用户实体类配置构造方法
//objs数据位置和excel上传位置一致。 public User(Object[] objs, String companyId, String companyName) { //默认手机号excel读取为字符串会存在科学记数法问题,转化处理 this.mobile = new DecimalFormat("#").format(objs[2]); this.username = objs[1].toString(); this.createTime = new Date(); this.timeOfEntry = (Date) objs[5]; this.formOfEmployment = ((Double) objs[4]).intValue(); this.workNumber = new DecimalFormat("#").format(objs[3]).toString(); this.companyId = companyId; this.companyName = companyName; }
自定义注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ExcelAttribute { /** 对应的列名称 */ String name() default ""; /** 列序号 */ int sort(); /** 字段类型对应的格式 */ String format() default ""; }
在实体类上使用上面的注解
@Entity @Table(name = "bs_user") @Getter @Setter @NoArgsConstructor public class User implements Serializable { private static final long serialVersionUID = -7611123338481573454L; @Id private String id; /*手机号码(最新版idea2018.2.5版)超详细超简单版 16841*/ @ExcelAttribute(sort = 2) private String mobile; @ExcelAttribute(sort = 1) private String username;//用户名 private String password;//密码 private Integer enableState;//启用状态 0为禁用 1为启用 private Date createTime;//创建时间 private String companyId; private String companyName; //部门 @ExcelAttribute(sort = 6) private String departmentId; //入职时间 @ExcelAttribute(sort = 5) private Date timeOfEntry; //聘用形式 @ExcelAttribute(sort = 4) private Integer formOfEmployment; //工号 @ExcelAttribute(sort = 3) private String workNumber; private String formOfManagement;//管理形式 private String workingCity;//工作城市 private Date correctionTime; //转正时间 private Integer inServiceStatus; //在职状态 1.在职 2.离职 private String departmentName;//部门名称 /** * level 水平,级,级别 * String * saasAdmin:saas管理员具备所有权限 * coAdmin:企业管理(创建租户企业的时候添加) * user:普通用户(需要分配角色) */ private String level; private String staffPhoto; //用户头像
导入的工具类
public class ExcelImportUtil<T> { private Class clazz; //T的类型 private Field fields[];//T类的字段数组 public ExcelImportUtil(Class clazz) { this.clazz = clazz; fields=clazz.getDeclaredFields(); } /*基于注解读取excel*/ public List<T> readExcel(InputStream is, int rowIndex, int cellIndex) { ArrayList<T> list = new ArrayList<>(); T entity = null; try { XSSFWorkbook workbook = new XSSFWorkbook(is); XSSFSheet sheet = workbook.getSheetAt(0); //不准确 int lastLength = sheet.getLastRowNum(); for (int rowNum = rowIndex; rowNum <= sheet.getLastRowNum(); rowNum++) { //每行的数据 Row row = sheet.getRow(rowNum); //创建每行对应的java对象 entity = (T) clazz.newInstance(); for (int j = cellIndex; j < row.getLastCellNum(); j++) { Cell cell = row.getCell(j); for (Field field : fields) {//遍历对象的每个属性 if (field.isAnnotationPresent(ExcelAttribute.class)) {//如果属性带有ExcelAttribute是否存在注解 field.setAccessible(true); ExcelAttribute excelAttribute = field.getAnnotation(ExcelAttribute.class); if (j == excelAttribute.sort()) {//如果这个单元格等于注解的sort属性时 //把这entity对象的field设置值。 field.set(entity, covertAttrType(field, cell)); } } } } list.add(entity); } } catch (Exception e) { e.printStackTrace(); } return list; } /** * 类型转换 将cell 单元格格式转为 字段类型 */ private Object covertAttrType(Field field, Cell cell) throws Exception { String fieldType = field.getType().getSimpleName(); if ("String".equals(fieldType)) { return getValue(cell); } else if ("Date".equals(fieldType)) { return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(getValue(cell)); } else if ("int".equals(fieldType) || "Integer".equals(fieldType)) { System.out.println(getValue(cell)); return Integer.parseInt(getValue(cell)); } else if ("double".equals(fieldType) || "Double".equals(fieldType)) { return Double.parseDouble(getValue(cell)); } else { return null; } } //格式转换为String public String getValue(Cell cell) { if (cell == null) { return ""; } switch (cell.getCellType()) { case STRING: return cell.getRichStringCellValue().getString().trim(); case NUMERIC: if (DateUtil.isCellDateFormatted(cell)) { Date javaDate = DateUtil.getJavaDate(cell.getNumericCellValue()); return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(javaDate); } else {//如果不是日期就是数字 String strCell = ""; Double nun = cell.getNumericCellValue(); BigDecimal bigDecimal = new BigDecimal(nun.toString()); if (bigDecimal != null) { strCell = bigDecimal.toPlainString(); } //去掉 浮点型 自动加 .0 if (strCell.endsWith(".0")) { strCell = strCell.substring(0, strCell.indexOf(".")); } return strCell; } case BOOLEAN: return String.valueOf(cell.getBooleanCellValue()); default: return ""; } } }
要导入的excel格式
POI报表导出
模板打印
概述:
自定义生成Excel报表文件还是有很多不尽如意的地方,特别是针对复杂报表头,单元格样式,字体等操作。手写这些代码不仅费时费力,有时候效果还不太理想。那怎么样才能更方便的对报表样式,报表头进行处理呢?答案是使用已经准备好的Excel模板,只需要关注模板中的数据即可。
模板打印的操作步骤
1. 制作模版文件(模版文件的路径)
2. 导入(加载)模版文件,从而得到一个工作簿
3. 读取工作表
4. 读取行
5. 读取单元格
6. 读取单元格样式
7. 设置单元格内容
8. 其他单元格就可以使用读到的样式了
//1.构造数据 List<需要的类型>list=.. //2.加载模板流数据 Resource resource = new ClassPathResource("excel-template/hr-demo.xlsx"); FileInputStream fis = new FileInputStream(resource.getFile()); //3.根据文件流,加载指定的工作簿 XSSFWorkbook wb = new XSSFWorkbook(fis); //4.读取工作表 Sheet sheet = wb.getSheetAt(0); //5.抽取公共的样式 Row styleRow = sheet.getRow(2); CellStyle [] styles = new CellStyle[styleRow.getLastCellNum()]; for(int i=0;i<styleRow.getLastCellNum();i++) { styles[i] = styleRow.getCell(i).getCellStyle(); } //6.构造每行和单元格数据 AtomicInteger datasAi = new AtomicInteger(2); Cell cell = null; for (EmployeeReportResult report : list) { Row dataRow = sheet.createRow(datasAi.getAndIncrement()); .... } String fileName = URLEncoder.encode(month+"人员信息.xlsx", "UTF-8"); response.setContentType("application/octet-stream"); response.setHeader("content-disposition", "attachment;filename=" + new String(fileName.getBytes("ISO8859-1"))); response.setHeader("filename", fileName); wb.write(response.getOutputStream());
把上面的设置成导出工具类
@Getter @Setter public class ExcelExportUtil<T> { private int rowIndex; private int styleIndex; private String templatePath; private Class clazz; private Field fields[]; public ExcelExportUtil(Class clazz, int rowIndex, int styleIndex) { this.rowIndex = rowIndex; this.styleIndex = styleIndex; this.clazz = clazz; fields = clazz.getDeclaredFields(); } /*基于注解导出*/ public void export(HttpServletResponse response, InputStream is, List<T> objs, String fileName) throws Exception { //Apache Poi提供了SXSSFWork对象,专门用于处理大数据量Excel报表导出 XSSFWorkbook workbook = new XSSFWorkbook(is); //SXSSFWorkbook workbook = new SXSSFWorkbook(workbook1); Sheet sheet = workbook.getSheetAt(0); Row row = sheet.getRow(styleIndex); //单元格的类型 CellStyle[] styles = new CellStyle[row.getLastCellNum()]; for (int i = 0; i < row.getLastCellNum(); i++) { styles[i] = row.getCell(i).getCellStyle(); } AtomicInteger atomicInteger = new AtomicInteger(rowIndex); for (T obj : objs) { Row row1 = sheet.createRow(atomicInteger.getAndIncrement()); for (int i = 0; i < styles.length; i++) { Cell cell = row1.createCell(i); cell.setCellStyle(styles[i]); for (Field field : fields) { if (field.isAnnotationPresent(ExcelAttribute.class)) { field.setAccessible(true); ExcelAttribute ea = field.getAnnotation(ExcelAttribute.class); if (i == ea.sort()) { if (field.get(obj) != null) { cell.setCellValue(field.get(obj).toString()); } } } } } } fileName = URLEncoder.encode(fileName, "UTF-8"); response.setContentType("application/octet-stream"); response.setHeader("content-disposition", "attachment;filename=" + new String(fileName.getBytes("ISO8859-1"))); response.setHeader("filename", fileName); workbook.write(response.getOutputStream()); } /*Apache Poi提供了SXSSFWork对象,专门用于处理大数据量Excel报表导出*/ public void exportBySXSSF(HttpServletResponse response, InputStream is, List<T> objs, String fileName) throws Exception { SXSSFWorkbook workbook = new SXSSFWorkbook(new XSSFWorkbook(is)); Sheet sheet = workbook.getSheetAt(0); //单元格的类型 CellStyle[] styles = getTemplateStyles(sheet.getRow(styleIndex)); AtomicInteger atomicInteger = new AtomicInteger(rowIndex); for (T obj : objs) { Row row1 = sheet.createRow(atomicInteger.getAndIncrement()); for (int i = 0; i < styles.length; i++) { Cell cell = row1.createCell(i); cell.setCellStyle(styles[i]); for (Field field : fields) { if (field.isAnnotationPresent(ExcelAttribute.class)) { field.setAccessible(true); ExcelAttribute ea = field.getAnnotation(ExcelAttribute.class); if (i == ea.sort()) { if (field.get(obj) != null) { cell.setCellValue(field.get(obj).toString()); } } } } } } fileName = URLEncoder.encode(fileName, "UTF-8"); response.setContentType("application/octet-stream"); response.setHeader("content-disposition", "attachment;filename=" + new String(fileName.getBytes("ISO8859-1"))); response.setHeader("filename", fileName); workbook.write(response.getOutputStream()); } public CellStyle[] getTemplateStyles(Row row) { CellStyle [] styles = new CellStyle[row.getLastCellNum()]; for(int i=0;i<row.getLastCellNum();i++) { styles[i] = row.getCell(i).getCellStyle(); } return styles; } }
完成导入
/** * 采用模版打印的形式完成报表导出,sxssf不支持模版导出 * 根据企业id和年月查询 */ @RequestMapping(value = "/export/{month}",method = RequestMethod.GET) public void export(@PathVariable(name = "month") String month) throws Exception { //1.构造表格数据 List<EmployeeReportResult> list = userCompanyPersonalService.findByReport(companyId, month); //2.加载模板 ClassPathResource resource = new ClassPathResource("excel-template/hr-demo.xlsx"); FileInputStream fis = new FileInputStream(resource.getFile()); //3.通过工具类下载文件 new ExcelExportUtil(EmployeeReportResult.class,2,2).exportBySXSSF(response,fis,list,month+"人事报表.xlsx"); }
百万数据报表概述
我们都知道Excel可以分为早期的Excel2003版本(使用POI的HSSF对象操作)和Excel2007版本(使用POI的XSSF操作),两者对百万数据的支持如下:
Excel 2003:在POI中使用HSSF对象时,excel 2003最多只允许存储65536条数据,一般用来处理较少的数据量。这时对于百万级别数据,Excel肯定容纳不了。
Excel 2007:当POI升级到XSSF对象时,它可以直接支持excel2007以上版本,因为它采用ooxml格式。这时excel可以支持1048576条数据,单个sheet表就支持近百万条数据。但实际运行时还可能存在问题,原因是执
行POI报表所产生的行对象,单元格对象,字体对象,他们都不会销毁,这就导致OOM的风险。
这时我们可以使用java的jdk性能监控工具
没有性能监控工具一切推论都只能停留在理论阶段,我们可以使用Java的性能监控工具来监视程序的运行情况,包括CUP,垃圾回收,内存的分配和使用情况,这让程序的运行阶段变得更加可控,也可以用来证明我们的推测。这里
我们使用JDK提供的性能工具Jvisualvm来监控程序运行。或者使用在线分析诊断工具Arthas。
Jvisualvm的使用
Jvisualvm位于JAVA_HOME/bin目录下,直接双击就可以打开该程序。如果只是监控本地的java进程,是不需要配置参数的,直接打开就能够进行监控。首先我们需要在本地打开一个Java程序,例如我打开员工微服务进程,这时
在jvisualvm界面就可以看到与IDEA相关的Java进程了:
Jvisualvm使用起来比较简单,双击点击当前运行的进程即可进入到程序的监控界面
解决方案分析
对于百万数据量的Excel导入导出,只讨论基于Excel2007的解决方法。在ApachePoi 官方提供了对操作大数据量的导入导出的工具和解决办法,操作Excel2007使用XSSF对象,可以分为三种模式:
用户模式:用户模式有许多封装好的方法操作简单,但创建太多的对象,非常耗内存(之前使用的方法)
事件模式:基于SAX方式解析XML,SAX全称Simple API for XML,它是一个接口,也是一个软件包。它是一
种XML解析的替代方法,不同于DOM解析XML文档时把所有内容一次性加载到内存中的方式,它逐行扫描文
档,一边扫描,一边解析。
SXSSF对象:是用来生成海量excel数据文件,主要原理是借助临时存储空间生成excel
这是一张Apache POI官方提供的图片,描述了基于用户模式,事件模式,以及使用SXSSF三种方式操作Excel的特性以及CUP和内存占用情况。
使用Apache POI完成百万数据量的Excel报表导出 解决方案
基于XSSFWork导出Excel报表,是通过将所有单元格对象保存到内存中,当所有的Excel单元格全部创建完成之后一次性写入到Excel并导出。当百万数据级别的Excel导出时,随着表格的不断创建,内存中对象越来越多,直至内
存溢出。Apache Poi提供了SXSSFWork对象,专门用于处理大数据量Excel报表导出。
在实例化SXSSFWork这个对象时,可以指定在内存中所产生的POI导出相关对象的数量(默认100),一旦内存中的对象的个数达到这个指定值时,就将内存中的这些对象的内容写入到磁盘中(XML的文件格式),就可以将这些
对象从内存中销毁,以后只要达到这个值,就会以类似的处理方式处理,直至Excel导出完成。
代码实现
// SXSSFWorkbook使用创建工作簿 SXSSFWorkbook workbook = new SXSSFWorkbook();
对比测试
百万数据报表读取
用户模式:加载并读取Excel时,是通过一次性的将所有数据加载到内存中再去解析每个单元格内容。当Excel数据量较大时,由于不同的运行环境可能会造成内存不足甚至OOM异常。
事件模式:它逐行扫描文档,一边扫描一边解析。由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中,这对于大型文档的解析是个巨大优势。
步骤分析:
(1)设置POI的事件模式
根据Excel获取文件流
根据文件流创建OPCPackage
创建XSSFReader对象
(2)Sax解析
自定义Sheet处理器
创建Sax的XmlReader对象
设置Sheet的事件处理器
逐行读取
原理分析
我们都知道对于Excel2007的实质是一种特殊的XML存储数据,那就可以使用基于SAX的方式解析XML完成Excel的读取。SAX提供了一种从XML文档中读取数据的机制。它逐行扫描文档,一边扫描一边解析。由于应用程序只是在
读取数据时检查数据,因此不需要将数据存储在内存中,这对于大型文档的解析是个巨大优势
代码实现
//自定义Sheet基于Sax的解析处理器 public class SheetHandler implements XSSFSheetXMLHandler.SheetContentsHandler { //封装实体对象 private PoiEntity entity; /** * 解析行开始 */ @Override public void startRow(int rowNum) { if (rowNum >0 ) { entity = new PoiEntity(); } } /** * 解析每一个单元格 */ @Override public void cell(String cellReference, String formattedValue, XSSFComment comment) { if(entity != null) { switch (cellReference.substring(0, 1)) { case "A": entity.setId(formattedValue); break; case "B": entity.setBreast(formattedValue); break; case "C": entity.setAdipocytes(formattedValue); break; case "D": entity.setNegative(formattedValue); break; case "E": entity.setStaining(formattedValue); break; case "F": entity.setSupportive(formattedValue); break; default: break; } } } /** * 解析行结束 */ public void endRow(int rowNum) { System.out.println(entity); } //处理头尾 public void headerFooter(String text, boolean isHeader, String tagName) { } }
自定义解析
/** * 自定义Excel解析器 */ public class ExcelParser { public void parse (String path) throws Exception { //1.根据Excel获取OPCPackage对象 OPCPackage pkg = OPCPackage.open(path, PackageAccess.READ); try { //2.创建XSSFReader对象 XSSFReader reader = new XSSFReader(pkg); //3.获取SharedStringsTable对象 SharedStringsTable sst = reader.getSharedStringsTable(); //4.获取StylesTable对象 StylesTable styles = reader.getStylesTable(); //5.创建Sax的XmlReader对象 XMLReader parser = XMLReaderFactory.createXMLReader(); //6.设置处理器 parser.setContentHandler(new XSSFSheetXMLHandler(styles,sst, new SheetHandler(), false)); XSSFReader.SheetIterator sheets = (XSSFReader.SheetIterator) reader.getSheetsData(); //7.逐行读取 while (sheets.hasNext()) { InputStream sheetstream = sheets.next(); InputSource sheetSource = new InputSource(sheetstream); try { parser.parse(sheetSource); } finally { sheetstream.close(); } } } finally { pkg.close(); } } }
对比测试
用户模式下读取测试Excel文件直接内存溢出,测试Excel文件映射到内存中还是占用了不少内存;事件模式下可以流畅的运行。
(1)使用用户模型解析
(2)使用事件模型解析
总结
通过简单的分析以及运行两种模式进行比较,可以看到用户模式下使用更简单的代码实现了Excel读取,但是在读取大文件时CPU和内存都不理想;而事件模式虽然代码写起来比较繁琐,但是在读取大文件时CPU和内存更加占优。
请尝试网页搜索
请尝试网页搜索
posted on 2021-01-03 05:04 fyccaishao 阅读(246) 评论(0) 编辑 收藏 举报