使用POI中XSSF 实现“合并单元格时,仅保留左上角单元格的值,而放弃其余的值“ 问题排查笔记

1、背景介绍
        在项目中借助POI 和 JXLS 两个开源工具jar实现excel数据导出,原有使用POI->HSSF方式进行数据导出,随着导出数据量的增大远远超出单sheet 65535条上限,将导出方式由POI->HSSF升级为POI->XSSF方式。
        术语说明:
       
JXLS:国外经常使用数据模版导出工具,easyPOI 是国内经常使用数据模版导出工具,
        POI->HSSF方式:支持Excel 97-2007版本的文件导出,单个sheet页最多能到导出65535条记录
        POI->XSSF方式:支持Excel2007以上版本的文件导出,单个sheet无条数限制html

2、核心代码
        maven jar包依赖apache

<dependency>
	<groupId>org.apache.poi</groupId>
	<artifactId>poi</artifactId>
	<version>3.15</version>
</dependency>
<dependency>
	<groupId>org.apache.poi</groupId>
	<artifactId>poi-ooxml</artifactId>
	<version>3.15</version>
</dependency>
<dependency>
	<groupId>org.apache.poi</groupId>
	<artifactId>poi-ooxml-schema</artifactId>
	<version>3.15</version>
</dependency>
<dependency>
	<groupId>net.sf.jxls</groupId>
	<artifactId>jxls-core</artifactId>
	<version>1.0.6</version>
</dependency>

     HSSF方式:api

import net.sf.jxls.transformer.XLSTransformer;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.util.CellRangeAddress;
try{
	//JXLS文件模版地址 文件配置详参:http://jxls.sourceforge.net/getting_started.html
	String outFilePath ="xxxx/file.xls";
	//数据集合
	Map<String, Object> dataMap = new HashMap<>();
	//数据赋值 略
	dataMap.put("users",new ArrayList<>());
	//加载JXLS文件模版
	InputStream fileStream = new FileInputStream(new File(outFilePath));
	//对JXLS文件模版 进行数据赋值,数据来源有
	XLSTransformer transformer = new XLSTransformer();
	HSSFWorkbook workbook = (HSSFWorkbook) transformer.transformXLS(
			fileStream, dataMap);
	//获取 excel 文件的第一个sheet页
	HSSFSheet sheet = workbook.getSheetAt(0);
	// 合并单元格操做  详参:https://poi.apache.org/apidocs/4.0/
	// 将第一个sheet页中的 第4列 第1行到16行进行单元格合并(行和列从0开始)
	sheet.addMergedRegion(new CellRangeAddress(1, 17, 3, 3));
	// 数据文件导出到指定目录   简化
	FileOutputStream fout = new FileOutputStream("xxxx/exportData.xls");
	workbook.write(fout);
	fout.close();
}catch (Throwable e){
	//......
}

  XSSF方式:app

import net.sf.jxls.transformer.XLSTransformer;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.ss.util.CellRangeAddress;
try{
	//JXLS文件模版地址 文件配置详参:http://jxls.sourceforge.net/getting_started.html
	String outFilePath ="xxxx/file.xlsx";
	//.....同上
	XSSFWorkbook workbook = (XSSFWorkbook) transformer.transformXLS(
			fileStream, dataMap);
	//获取 excel 文件的第一个sheet页
	XSSFSheet sheet = workbook.getSheetAt(0);
	// 合并单元格操做  详参:https://poi.apache.org/apidocs/4.0/
	// 将第一个sheet页中的 第4列 第1行到16行进行单元格合并(行和列从0开始)
	sheet.addMergedRegion(new CellRangeAddress(1, 17, 3, 3));
	// 数据文件导出到指定目录   简化
	FileOutputStream fout = new FileOutputStream("xxxx/exportData.xlsx");
	workbook.write(fout);
	fout.close();
}catch (Throwable e){
	//......
}

3、XSSF问题说明
       
正主来了,升级为POI->XSSF方式后,发现生成的excel文件,合并单元格列以下所示:
xss

发现合并后的列中的值并未清空,致使excel默认计数和求和数值不正确。经排查发现,由于POI->XSSF中进行addMergedRegion 合并单元格时并不支持“合并单元格时,仅保留左上角单元格的值,而放弃其余的值”。在excel2007版本在进行合并单元格时会默认提示以下所示:maven

排查了到POI 的4.1.0 版本为止,都不存在合并单元格时保留仅保留坐上角值的相关配置,因而技术上搞不定的事只能经过业务手段搞定,与业务沟通后退求其次,容许excel默认计数值错误,保证excel默认求和正确,具体操做方式以下所示:工具

try{
	//.....同上略
	XSSFWorkbook workbook = (XSSFWorkbook) transformer.transformXLS(
			fileStream, dataMap);
	//获取 excel 文件的第一个sheet页
	XSSFSheet sheet = workbook.getSheetAt(0);
	//将2到16行 第3列的值设置为0 
	for(int i=2;i<17;i++){
		sheet.getRow(i).getCell(3).setCellValue(0);
	}
	//.....同上略
}catch (Throwable e){
	//......
}

最终经过手动将需合并的1~16行中第3列,保留第1行第3列的值,将第2行到第6行的第3列的值设置为0的方式保证导出的excel文件中默认求和数据的正确性。this

4、XSSF问题说明
1.IllegalArgumentException: Merged region A4 must contain 2 or more cells
   缘由:因为CellRangeAddress 四个参数配置错误,计算公式为 (_lastRow - _firstRow + 1)*(_lastCol - _firstCol + 1)<2 ,经过计算公式算出可合并的行数小于2,说明没有可合并的列或行致使报错.net


2.org.apache.poi.openxml4j.exceptions.OLE2NotOfficeXmlFileException: The supplied data appears to be in the OLE2 Format. You are calling the part of POI that deals with OOXML (Office Open XML) Documents. You need to call a different part of POI to process this data (eg HSSF instead of XSSF) 
   缘由:在使用POI->XSSF时,使用的FileInputStream 模版文件格式为file.xls致使问题产生,将模版另存为.xlsxexcel