本文主要记录Excel导入及模板下载,遇到的问题及注意事项。

第一节:Excel导入

 
1、如何获取Excel中的最大行,也就是最后一行?
2、如何获取有效行?有效行的定义是每一行记录中每一列中值都不为空。
3、如何读取每一个cell中的值,无论是什么类型的
 
到导入Excel操作时,发现当导入的Excel内容有格式,或者空的行有格式,读取会和预想的不一样。使用sheet.getLastRowNum()获取最后一行是不准确的。网上查了,也没有找到有效的方法。这里不知道大家有好的方法没,或者大家在项目中怎么导入?
 
总结一下,共有如下两个问题:
 
问题一:如何获取正确的Excel的有效最大行。
 
首先搞明白为什么需要获取Excel的有效最大行,因为考虑到Excel中数据量太大,有可能会响应超时,所以在导入之前要判断是否满足系统要求的导入最大行数,超出最大行,抛出异常。
在实现过程中发现如果Excel中有效行只有一行,其他有200行是有样式的空内容,那么使用sheet.getLastRowNum()获取的最大行数是202行(加表头),获得202行这个数字是错误的,实际Excel中的有效最大行是1行。
我的处理方式是:
首先,定义一个List集合,用来存储有效的Row。
那么什么是有效的Row呢?这个标准是什么?假如导入的模板要求一行是七列,那么判断每一行的这个七个Cell满足 不为null且不为"",,则为有效行。
循环这个Excel,将满足条件的有效行add中List集合中,获取这个List集合的size值,通过size值来校验Excel内容的最大行数,这个数字基本上可以认为是正确的。
 
具体实现看 下面这块代码:
 /**
* @param returnMap
* @Title: dealExcelData
* @Description: TODO(保存Excel中的数据,并过滤重复的记录)
* @author: yanghai * @param: @param contents 存储 Excel中的内容
* @param: @param item 上传的Excel元素
* @param: @param request
* @param: @param repeatCount
* @return: void
* @throws
*/
private void dealExcelData(List<CompanyInvoiceRecord> contents, MultipartFile item, HttpServletRequest request, Integer repeatCount, Map<String, Object> returnMap) throws Exception
{
List<Integer> l = new ArrayList<Integer>();
Integer count = 0;
CompanyInvoiceRecord dto = null; //临时文件名称
String tempDir = "/files-" +DateTool.formatDate(System.currentTimeMillis(), "yyyy-MM-dd-HH-mm");
//临时文件全路径
String tempFileDir = request.getSession().getServletContext().getRealPath(tempDir);
//创建临时文件
File tempFile = new File(tempFileDir);
if(!tempFile.exists())
{
tempFile.mkdir();
} //获取原始文件全名称
String originalFilename = item.getOriginalFilename();
// 获取文件后缀
String suffix = "";
try
{
suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
}
catch (Exception e)
{
e.printStackTrace();
throw new Exception("没有文件信息!");
}
//完整的文件目录
String fileName = tempFileDir + File.separator + originalFilename;
File newFile = new File(fileName); try
{
// 保存到一个目标文件中。
item.transferTo(newFile);
}
catch (Exception e)
{
e.printStackTrace();
throw new Exception("保存上传Excel文件失败!");
}
Workbook wb = null; FormulaEvaluator formulaEvaluator = null; try
{
FileInputStream inputStream = new FileInputStream(newFile); if(suffix.endsWith("xls"))
{
wb = new HSSFWorkbook(inputStream); formulaEvaluator = new HSSFFormulaEvaluator((HSSFWorkbook) wb);
}
else
{
wb = new XSSFWorkbook(inputStream); formulaEvaluator = new XSSFFormulaEvaluator((XSSFWorkbook) wb);
}
}
catch (IOException e)
{
// 删除目录
deleteDir(new File(tempFileDir));
e.printStackTrace(); } // 保存有效的 Excel行
List<Row> rowList = new ArrayList<Row>(); Sheet sheet = wb.getSheetAt(0);
if(null == sheet)
{
deleteDir(new File(tempFileDir));
throw new Exception("导入失败:导入文件中不存在sheet页!");
}
else
{
/*int lastRowNum = sheet.getLastRowNum();
System.out.println("==============="+lastRowNum);
if(lastRowNum > 5001)
{
throw new Exception("超过导入上限。最多导入5000条!");
}*/
try
{
for(Row row : sheet)
{
// 校验表头
if(row.getRowNum() == 0)
{
if(StringUtils.isNotEmpty(row.getCell(0).toString().trim()) && "付款日期".equals(row.getCell(0).toString().trim())
&& StringUtils.isNotEmpty(row.getCell(1).toString().trim()) && "发票号".equals(row.getCell(1).toString().trim())
&& StringUtils.isNotEmpty(row.getCell(2).toString().trim()) && "金额".equals(row.getCell(2).toString().trim())
&& StringUtils.isNotEmpty(row.getCell(3).toString().trim()) && "税额".equals(row.getCell(3).toString().trim())
&& StringUtils.isNotEmpty(row.getCell(4).toString().trim()) && "合计".equals(row.getCell(4).toString().trim())
&& StringUtils.isNotEmpty(row.getCell(5).toString().trim()) && "公司名称".equals(row.getCell(5).toString().trim())
&& StringUtils.isNotEmpty(row.getCell(6).toString().trim()) && "货物名称".equals(row.getCell(6).toString().trim())
&& StringUtils.isNotEmpty(row.getCell(7).toString().trim()) && "申请人".equals(row.getCell(7).toString().trim())
&& StringUtils.isNotEmpty(row.getCell(8).toString().trim()) && "申请金额".equals(row.getCell(8).toString().trim())
)
{
continue;
}
else
{
deleteDir(new File(tempFileDir));
throw new Exception("表头信息错误!");
}
}
else if(row.getRowNum() >= 1)
{ try
{
if((null == row.getCell(0) || String.valueOf(row.getCell(0)).equals(""))
&& (null == row.getCell(1) || String.valueOf(row.getCell(1)).equals(""))
&& (null == row.getCell(2) || String.valueOf(row.getCell(2)).equals(""))
&& (null == row.getCell(3) || String.valueOf(row.getCell(3)).equals(""))
&& (null == row.getCell(4) || String.valueOf(row.getCell(4)).equals(""))
&& (null == row.getCell(5) || String.valueOf(row.getCell(5)).equals(""))
&& (null == row.getCell(6) || String.valueOf(row.getCell(6)).equals(""))
&& (null == row.getCell(7) || String.valueOf(row.getCell(7)).equals(""))
&& (null == row.getCell(8) || String.valueOf(row.getCell(8)).equals("")))
{
System.out.println("===公司费用 发票 导入 记录 导入===此行"+row.getRowNum()+"为空");
}
else
{
rowList.add(row);
}
} catch (Exception e1)
{
e1.printStackTrace();
}
}
} if(null != rowList && rowList.size() > 0)
{
int lastRowNum = rowList.size();
System.out.println("==============="+lastRowNum);
if(lastRowNum > 5001)
{
throw new Exception("超过导入上限。最多导入5000条!");
} for(Row row : rowList)
{
try
{
dto = new CompanyInvoiceRecord(); short lastCellNum = row.getLastCellNum();
if(lastCellNum < 1)
{
deleteDir(new File(tempFileDir));
throw new Exception("第" + row.getRowNum() + "行列数不足!");
} if(StringUtils.isNotEmpty(row.getCell(0).toString()))
{
// 付款日期
dto.setPayDate(readCellToStringToTrim(row.getCell(0)).toString().trim());
} row.getCell(1).setCellType(Cell.CELL_TYPE_STRING);
if(StringUtils.isNotEmpty(row.getCell(1).toString().trim()))
{
// 发票号码
dto.setInvoiceNum(readCell(row.getCell(1)).toString().trim());
}
if(StringUtils.isNotEmpty(row.getCell(2).toString()))
{
// 金额
dto.setAmount(readCell(row.getCell(2)).toString().trim());
}
if(StringUtils.isNotEmpty(row.getCell(3).toString()))
{
// 税额
dto.setTaxAmount(readCell(row.getCell(3)).toString().trim());
}
if(StringUtils.isNotEmpty(row.getCell(4).toString()))
{
// 合计
dto.setTotalAmount(readCell(row.getCell(4)).toString().trim());
}
if(StringUtils.isNotEmpty(row.getCell(5).toString()))
{
// 抬头
dto.setTitle(readCell(row.getCell(5)).toString().trim());
}
if(StringUtils.isNotEmpty(row.getCell(6).toString()))
{
// 货物名称
dto.setGoodsName(readCell(row.getCell(6)).toString().trim());
}
if(StringUtils.isNotEmpty(row.getCell(7).toString()))
{
// 申请人
dto.setApplicantName(readCell(row.getCell(7)).toString().trim());
}
if(StringUtils.isNotEmpty(row.getCell(8).toString()))
{
// 申请金额
dto.setApplyAmount(readCell(row.getCell(8)).toString().trim());
} if(StringUtils.isEmpty(dto.getPayDate()) && StringUtils.isEmpty(dto.getInvoiceNum())
&& StringUtils.isEmpty(dto.getAmount()) && StringUtils.isEmpty(dto.getTitle())
&& StringUtils.isEmpty(dto.getTaxAmount()) && StringUtils.isEmpty(dto.getTotalAmount())
&& StringUtils.isEmpty(dto.getGoodsName()) && StringUtils.isEmpty(dto.getApplicantName())
&& StringUtils.isEmpty(dto.getApplyAmount()))
{ }
else
{
Boolean flag = true;
if(contents.contains(dto)) // 已包含
{
repeatCount++;
flag = false;
}
if(flag)
{
contents.add(dto);
}
}
} catch (Exception e)
{
l.add(row.getRowNum()+1);
count++;
System.out.println("==公司费用导入异常:"+e);
}
}
} returnMap.put("count", count);
returnMap.put("repeatCount", repeatCount); System.out.println("======异常条数:"+count+",发生异常的行数分别是:"+l.toString());
// 删除excel
deleteDir(new File(tempFileDir)); } catch (Exception e)
{
e.printStackTrace();
deleteDir(new File(tempFileDir));
throw new Exception("请按照要求填写Excel的内容!");
}
}
} /**
* 删除文件夹及文件夹下的内容
* @param dir
* @return
*/
private boolean deleteDir(File dir)
{
if (dir.isDirectory())
{
String[] children = dir.list();
// 递归删除目录中的子目录下
for (int i = 0; i < children.length; i++)
{
boolean success = deleteDir(new File(dir, children[i]));
if (!success)
{
return false;
}
}
}
// 目录此时为空,可以删除
return dir.delete();
}
问题二:如何正确转换数据类型。
 
解释一下这个问题,假如我这一列的数据名称是“发票号码”,是由数字组成的字符串,那么在保存在数据库中,期望保存的是“123456”,而不是“123456.0”,保存的有小数点,说明在读取Excel的内容是,当成了数字类型了,这就涉及到Excel中几种数据类型的转换了。
 
当然上面针对这个问题有个处理技巧,当知道这一列是字符串类型,可以直接读取cell中内容前将cellType设置为CELL_TYPE_STRING。
 
参考Cell接口源码,粘出Excel中Cell都有下面几种cellType:
 /**
* Numeric Cell type (0)
* @see #setCellType(int)
* @see #getCellType()
*/
public final static int CELL_TYPE_NUMERIC = 0; // 数字类型 /**
* String Cell type (1)
* @see #setCellType(int)
* @see #getCellType()
*/
public final static int CELL_TYPE_STRING = 1; // 字符串类型 /**
* Formula Cell type (2)
* @see #setCellType(int)
* @see #getCellType()
*/
public final static int CELL_TYPE_FORMULA = 2; // 公式类型 /**
* Blank Cell type (3)
* @see #setCellType(int)
* @see #getCellType()
*/
public final static int CELL_TYPE_BLANK = 3; // 空白类型 /**
* Boolean Cell type (4)
* @see #setCellType(int)
* @see #getCellType()
*/
public final static int CELL_TYPE_BOOLEAN = 4; // 布尔类型 /**
* Error Cell type (5)
* @see #setCellType(int)
* @see #getCellType()
*/
public final static int CELL_TYPE_ERROR = 5; // 错误类型
时间内容也属于CELL_TYPE_NUMERIC类型,如果是时间类型,进行相应的时间格式转换,否则不用做处理。时间格式在本人处理起来比较麻烦,开发前一定要做好约束规范,否则,在读取Excel中内容需要考虑各种类型,代码是控制不了的。
关于处理Excel中各种数据类型,这里本人整理了一个通用方法,基本上可以满足使用。
 
   /**
* @description:读取Excel单元格数据
* @param cell excel单元格
* @return String
*/
private static String readCell(Cell cell)
{
String cell_value = ""; if (cell != null)
{
switch (cell.getCellType())
{
case Cell.CELL_TYPE_BOOLEAN:
// 得到Boolean对象的方法
if (cell.getBooleanCellValue())
{
cell_value = "TRUE";
} else
{
cell_value = "FALSE";
}
break;
case Cell.CELL_TYPE_NUMERIC:
// 先看是否是日期格式
if (DateUtil.isCellDateFormatted(cell))
{
// 读取日期格式
cell_value = DateUtils.formatDate(cell.getDateCellValue(), "yyyy-MM-dd");
} else
{
// 读取数字
cell_value = String.valueOf(cell.getNumericCellValue());
}
break;
case Cell.CELL_TYPE_FORMULA:
// 读取公式的值
cell_value = cell.getCellFormula();
break;
case Cell.CELL_TYPE_STRING:
// 读取String
cell_value = cell.getRichStringCellValue().getString();
break;
case Cell.CELL_TYPE_ERROR:
cell_value = cell.getErrorCellValue() + "";
break;
case HSSFCell.CELL_TYPE_BLANK:
cell_value = "";
break;
default:
cell_value = "";
}
}
return cell_value;
}

第二节:Excel模板下载

 
关于模板下载,贴出实现代码。
 @RequestMapping("/downExcel")
public ModelAndView downBlack(HttpServletRequest request, HttpServletResponse response) throws IOException
{
String realPathName = "";
String tempPath = "";
String fileName = ""; BufferedInputStream bis = null;
BufferedOutputStream bos = null; try {
tempPath = request.getSession().getServletContext().getRealPath("/") + "/download/";
fileName = "batchReceivedTicketTemplate.xlsx";
realPathName = tempPath + fileName; long fileLength = new File(tempPath + fileName).length(); // 文件下载设置response
response.setContentType("text/html;charset=utf-8");
request.setCharacterEncoding("UTF-8");
response.setContentType("application/x-msdownload;"); // 火狐
if (request.getHeader("User-Agent").toLowerCase().indexOf("firefox") > 0)
{
response.setHeader("Content-disposition", "attachment; filename=" + new String("批量收票导入模板.xlsx".getBytes("utf-8"), "ISO8859-1"));
}
else
{
response.setHeader("Content-Disposition", "attachment;filename=" + new String("批量收票导入模板.xlsx".getBytes("gb2312"), "ISO8859-1"));
} response.setHeader("Content-Length", String.valueOf(fileLength)); // 从模板获取输入流
bis = new BufferedInputStream(new FileInputStream(realPathName)); // 输出流
bos = new BufferedOutputStream(response.getOutputStream()); // 读取文件流输出
byte[] buff = new byte[2048];
int bytesRead;
while (-1 != (bytesRead = bis.read(buff, 0, buff.length)))
{
bos.write(buff, 0, bytesRead);
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
finally
{
if (bis != null)
bis.close();
if (bos != null)
bos.close();
} return null;
}

这段代码,很清晰,在实现的过程中,要注意一点,就是模板的后缀要和下载到的模板文件后缀保持一致。否则在导入的时候会报下面这个异常信息提示:

以上内容均由本人实际工作中遇到的问题及个人总结,如有错误,欢迎大家指正!

使用 Apache poi 导入Excel的更多相关文章

  1. 项目一:第四天 1、快递员的条件分页查询-noSession,条件查询 2、快递员删除(逻辑删除) 3、基于Apache POI实现批量导入区域数据 a)Jquery OCUpload上传文件插件使用 b)Apache POI读取excel文件数据

    1. 快递员的条件分页查询-noSession,条件查询 2. 快递员删除(逻辑删除) 3. 基于Apache POI实现批量导入区域数据 a) Jquery OCUpload上传文件插件使用 b) ...

  2. Java 使用poi导入excel,结合xml文件进行数据验证的例子(增加了jar包)

    ava 使用poi导入excel,结合xml文件进行数据验证的例子(增加了jar包) 假设现在要做一个通用的导入方法: 要求: 1.xml的只定义数据库表中的column字段,字段类型,是否非空等条件 ...

  3. Java开发小技巧(六):使用Apache POI读取Excel

    前言 在数据仓库中,ETL最基础的步骤就是从数据源抽取所需的数据,这里所说的数据源并非仅仅是指数据库,还包括excel.csv.xml等各种类型的数据接口文件,而这些文件中的数据不一定是结构化存储的, ...

  4. 利用Apache POI操作Excel

    最近在做接口,有个功能是利用Excel导入汽车发动机所需零件信息到线上系统中.简单回顾一下之前学过的用java操作Excel. 1.maven配置Apache POI pom.xml中配置POIjar ...

  5. 在java poi导入Excel通用工具类示例详解

    转: 在java poi导入Excel通用工具类示例详解 更新时间:2017年09月10日 14:21:36   作者:daochuwenziyao   我要评论   这篇文章主要给大家介绍了关于在j ...

  6. apache POI 导出excel相关方法

    apache POI 操作excel无比强大.同时有操作word和ppt的接口. 下面讲解poi中常用方法. 1,设置列宽 HSSFSheet sheet = wb.getSheetAt(0); sh ...

  7. poi导入Excel,数字科学记数法转换

    在这里分享一下使用poi 导入Excel时 把数字转换为科学记数法的解决方法: 就是使用DecimalFormat对 i 进行了格式化 结果为:

  8. 使用Apache POI导出Excel小结--导出XLS格式文档

    使用Apache POI导出Excel小结 关于使用Apache POI导出Excel我大概会分三篇文章去写 使用Apache POI导出Excel小结--导出XLS格式文档 使用Apache POI ...

  9. Java使用Apache POI进行Excel导入和导出

    Manve依赖 <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml --> <dependency> ...

随机推荐

  1. linux系统下实时监控进程以及定位杀死挂起的进程

    一.实时监测进程 [root@instance-3lm099to ~]# top top - :: up days, min, users, load average: 0.01, 0.05, 0.0 ...

  2. Httpclient 实现带参文件上传

    这里直接贴出的是我封装好的doPostFile方法,httpclient 的版本是3.1. public static String doPostFile(String url, Part[] par ...

  3. lua学习笔记(十)

      模块与包  使用require加载模块     一个规范的模块应该返回一个table作为这个模块所有导出功能的集合     lua里没通过任何强制性语法规则要求创建模块时反回一个table     ...

  4. [C++]二维数组还是一维数组?

    记得刚学习C++那会这个问题曾困扰过我,后来慢慢形成了不管什么时候都用一维数组的习惯,再后来知道了在一维数组中提出首列元素地址进行二维调用的办法.可从来没有细想过这个问题,最近自己写了点代码测试下,虽 ...

  5. 通过Lock对象以及Condition对象实现多线程同步

    通过Lock对象以及Condition对象实现多线程同步: 在之前的学习中,无论是通过synchronized建立同步代码块,还是通过synchronized建立同步函数,都是把对象看成一把锁来实现同 ...

  6. linux下apache php配置redis

    1.安装redis 第一步: 下载:https://github.com/nicolasff/phpredis/archive/2.2.4.tar.gz 上传phpredis-2.2.4.tar.gz ...

  7. apache常用模块介绍

      mod_actions 基于媒体类型或请求方法,为执行CGI脚本而提供 mod_alias 提供从文件系统的不同部分到文档树的映射和URL重定向 mod_asis 发送自己包含HTTP头内容的文件 ...

  8. 基于SqlDependency的Asp.net数据缓存

    首先,确保目标数据库的is_broker_enabled已经enabled. SELECT name, is_broker_enabled FROM sys.databases 如果不是enabled ...

  9. 【BZOJ1038】[ZJOI2008]瞭望塔 半平面交

    [BZOJ1038][ZJOI2008]瞭望塔 Description 致力于建设全国示范和谐小村庄的H村村长dadzhi,决定在村中建立一个瞭望塔,以此加强村中的治安.我们将H村抽象为一维的轮廓.如 ...

  10. AFN errorCode对应的状态码

    转 http://blog.csdn.NET/wangyanchang21/article/details/50932191 在很多时候都会遇到错误, 还会带有一些 Error Code , 比如在各 ...