针对业务数据导入,网上有很多方法。大多数对超大数据量的处理支持得不是太好。主要有两个大的方面存在问题:

1.由于采用list,当面对大数据量的数据解析导入时,会出现OOM异常而无法解析超大数量。

2.通用性不好,一个excel一个类,代码重用性非常低。

当然还有一些点,比如解析验证、样例数据输出等。

本文档介绍如何将包含超大数据量的EXCEL2007文件,解析为业务对象,通过回调模式,交给回调来处理解析转化好的业务数据对象。

下面直接上代码

一、需要的包

<dependency>
             <groupId>org.apache.poi</groupId>
             <artifactId>poi</artifactId>
             <version>4.0.1</version>
         </dependency>
         <dependency>
             <groupId>org.apache.poi</groupId>
             <artifactId>poi-ooxml</artifactId>
             <version>4.0.1</version>
         </dependency>


 

二、excel2007解析器

解析器对excel2007文件逐行解析,并组装为业务对象。解析器通过反射机制,将行数据转化为业务对象。

使用者只要实现DataHandler,就可以处理转化好的业务数据,以及解析和组装数据出错时的错误信息。

解析器提供三方面功能:

1.解析和组织数据

2.获取前面一定行数的样例数据

3.测试解析组织,验证解析和组装时否有存在错误,并可以获得错误信息

 

源代码

import org.apache.poi.ooxml.util.SAXHelper;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackageAccess;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.util.CellAddress;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.binary.XSSFBSheetHandler.SheetContentsHandler;
import org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
import org.apache.poi.xssf.model.StylesTable;
import org.apache.poi.xssf.usermodel.XSSFComment;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;

import com.winshare.util.BeanUtils;
import com.winshare.util.DateFormatTool;

/**
 *  本解析器实现对excel2007版本的解析,本类采用回调模式,逐个Sheet逐行处理数据,转化为业务对象,
 *  本类通过反射机制,实现对业务对象属性值设定,通用化数据处理,方便开发。
 * 
 * @author tbr
 *
 * @param <T>
 */
public class ExcelParser<T> implements SheetContentsHandler{
	
	private File excelFile;
	private DataHandler<T> dataHandler;

	private int rowNum;
	private int colNum;
	transient private Map<Integer,String> sheetRow=new HashMap<>();
	private Class<T> clazz;
	private T data;
	private Map<Integer,String> fieldMap;
	
	//开始解析的行号,sheet行号从0开始编号。
	private int beginRowNum;
	
	transient private boolean isTestParse=false; 
	
	//样例数据区
	transient private boolean sampleParse=false;
	transient private int sampleRowNum=0;
	transient private List<Map<Integer,String>> sampleRows=new ArrayList<>() ;
	
	//记录处理过程中的错误信息,多余100个,记录前面100个错误信息
	transient private List<Throwable> errors=new ArrayList<>();
	
	public ExcelParser(String excelFile,Class<T> clazz,Map<Integer,String> fieldMap, DataHandler<T> dataHandler){
		this(new File(excelFile),clazz,fieldMap,0,dataHandler);
	}
	
	/**
	 * 构造解析器,后续调用解析方法,进行数据解析。
	 * @param excelFile      excel文件名
	 * @param clazz          业务对象类名,即将数据行解析为业务对象
	 * @param fieldMap       excel列对应的业务对象属性列表
	 * @param beginRowNum    开始解析的sheet行号,sheet行从0开始编号
	 * @param dataHandler    解析为业务对象后,业务对象的处理器
	 */
	public ExcelParser(String excelFile,Class<T> clazz,Map<Integer,String> fieldMap,int beginRowNum, DataHandler<T> dataHandler){
		this(new File(excelFile),clazz,fieldMap,beginRowNum,dataHandler);
	}
	
	public ExcelParser(File excelFile,Class<T> clazz,Map<Integer,String> fieldMap, DataHandler<T> dataHandler){
		this(excelFile,clazz,fieldMap,0,dataHandler);		
	}
	public ExcelParser(File excelFile,Class<T> clazz,Map<Integer,String> fieldMap,int beginRowNum, DataHandler<T> dataHandler){
		this.excelFile=excelFile;
		this.clazz=clazz;
		this.fieldMap=fieldMap;
		this.beginRowNum = beginRowNum;
		this.dataHandler=dataHandler;
	}
	
	/**
	 *   获取指定sheet的取样例数据
	 *   
	 * @param sheetNum      sheet号,编号从1开始。
	 * @param sampleRowNum  获取的样例行数
	 * @return
	 * @throws Exception
	 */
	public List<Map<Integer,String>> parseSampleRow(int sheetNum,int sampleRowNum) throws Exception{
		sampleParse=true;		
		this.sampleRowNum=sampleRowNum;
		if ( ! excelFile.exists() )
			return null;
		
		sampleRows.clear();
		errors.clear();
		
		OPCPackage pkg = null;
		ReadOnlySharedStringsTable sharedStringsTable = null;
        InputStream sheet = null;
		try{
			pkg= OPCPackage.open(excelFile,PackageAccess.READ);
			sharedStringsTable = new ReadOnlySharedStringsTable(pkg);
		
	        // 创建表阅读器
	        XSSFReader xssfReader = new XSSFReader(pkg);
	        StylesTable styles = xssfReader.getStylesTable();
	        
	        sheet=xssfReader.getSheet("rId"+sheetNum);
	        parseSheet(styles,sharedStringsTable,sheet);
		}finally{
			if (pkg!=null)
				pkg.close();
			if (sheet!=null)
				sheet.close();
		}
		
		return sampleRows;
	}
	
	/**
	 *   测试指定sheet的数据,逐行解析,转为业务数据对象,验证数据格式是否正确。
	 *   
	 * @param sheetNum      sheet号,编号从1开始。
	 * @param sampleRowNum
	 * @return
	 * @throws Exception
	 */
	public void testParse(int sheetNum) throws Exception{
		isTestParse=true; 
		doParse( sheetNum);
	}
	/**
	 *   解析指定sheet的数据,逐行解析,转为业务数据对象,并逐个业务对象调用处理器处理业务数据。
	 *   
	 * @param sheetNum      sheet号,编号从1开始。
	 * @param sampleRowNum
	 * @return
	 * @throws Exception
	 */
	public void parse(int sheetNum) throws Exception{
		isTestParse=false; 
		doParse( sheetNum);
	}
	public void doParse(int sheetNum) throws Exception{
		if ( ! excelFile.exists() )
			return ;
		sampleParse=false;
		errors.clear();

		OPCPackage pkg = null;
		ReadOnlySharedStringsTable sharedStringsTable = null;
        InputStream sheet = null;
		try{
			pkg= OPCPackage.open(excelFile,PackageAccess.READ);
			sharedStringsTable = new ReadOnlySharedStringsTable(pkg);
		
	        // 创建表阅读器
	        XSSFReader xssfReader = new XSSFReader(pkg);
	        StylesTable styles = xssfReader.getStylesTable();
	        sheet=xssfReader.getSheet("rId"+sheetNum);
	        parseSheet(styles,sharedStringsTable,sheet);

		}finally{
			if (pkg!=null)
				pkg.close();
			if (sheet!=null)
				sheet.close();
		}
		
	}
	/**
	 *   逐个sheet的测试,逐行解析,转为业务数据对象,验证数据格式是否正确。
	 *   
	 * @param sheetNum      sheet号,编号从1开始。
	 * @param sampleRowNum
	 * @return
	 * @throws Exception
	 */
	public void testParse() throws Exception{
		 isTestParse=true; 
		 doParse();
	}
	/**
	 * 逐个sheet、逐行解析,转为业务数据对象,并逐个业务对象调用处理器处理业务数据。
	 * 
	 * @throws Exception
	 */
	public void parse() throws Exception{
		 isTestParse=false; 
		 doParse();
	}
	public void doParse() throws Exception{
		if ( ! excelFile.exists() )
			return ;
		sampleParse=false;
		errors.clear();
		
		OPCPackage pkg = null;
		ReadOnlySharedStringsTable sharedStringsTable = null;
        InputStream sheet = null;
		try{
			pkg= OPCPackage.open(excelFile,PackageAccess.READ);
			sharedStringsTable = new ReadOnlySharedStringsTable(pkg);
		
	        // 创建表阅读器
	        XSSFReader xssfReader = new XSSFReader(pkg);
	        StylesTable styles = xssfReader.getStylesTable();
	        XSSFReader.SheetIterator sheets = (XSSFReader.SheetIterator) xssfReader.getSheetsData();
	        //逐个处理sheet,
	        while(sheets.hasNext()){
	        	sheet=sheets.next();
	        	parseSheet(styles,sharedStringsTable,sheet);
	        	sheet.close();
	        }
		}finally{
			if (pkg!=null)
				pkg.close();
			if (sheet!=null)
				sheet.close();
		}
	}
	
	private void parseSheet(StylesTable styles,ReadOnlySharedStringsTable sharedStringsTable,
			InputStream sheetInputStream) {
		XMLReader sheetParser;
		try {
			sheetParser = SAXHelper.newXMLReader();
		} catch (Exception e) {
			return ;
		} 
        DataFormatter formatter = new DataFormatter();
        InputSource sheetSource = new InputSource(sheetInputStream);
        ContentHandler handler = new XSSFSheetXMLHandler(styles, null, sharedStringsTable,
                this, formatter, false);
        sheetParser.setContentHandler(handler);
        try {
			sheetParser.parse(sheetSource);
		} catch (Exception e) {
			dataHandler.handleException(e);
			if (errors.size()<100)
				errors.add(e);
		}	
	}
	
	/**
	 * 获取解析错误信息
	 * 
	 * @return
	 */
	public List<Throwable> getErrors(){
		List<Throwable> retErrors=new ArrayList<>();
		retErrors.addAll(errors);
		return retErrors;
	}
	/**
	 * 行解析开始
	 */
	@Override
	public void startRow(int rowNum) {
		if ( rowNum < beginRowNum ){
			return;
		}
		this.rowNum=rowNum;
		this.colNum= -1 ;
	}
	/**
	 * 行解析完成
	 */
	@Override
	public void endRow(int rowNum) {
		if ( rowNum < beginRowNum ){
			return;
		}
		if (sampleParse){
			//读取样例行,不进行业务对象转化
			if ( sampleRows.size() < sampleRowNum ){
				Map<Integer,String> row=new HashMap<>();
				row.putAll(sheetRow);
				sampleRows.add(row);
			}
			return;
		}
		createEntity();
		if ( ! isTestParse)
			dataHandler.handleData(data);
	}
	
	private void createEntity(){
		try {
			data=clazz.newInstance();
		} catch (Exception e) {
			dataHandler.handleException(e);
			if (errors.size()<100)
				errors.add(e);
		}
		fieldMap.forEach((colIdx,fieldName)->{
			try {
				setDataValue(colIdx,fieldName);
			} catch (Exception e) {
				e.printStackTrace();
				if (errors.size()<100)
					errors.add(e);
			}
		});
	}
	private void setDataValue(int colIdx,String fieldName) throws Exception{
		String formattedValue=sheetRow.get(colIdx);
		String[] propNames=fieldName.split("\\.");
		Object currObj=data,field;
		String propName;
		Class<?> propClz;
		for(int i=0;i<propNames.length-1;i++){
			propName=propNames[i];
			field=BeanUtils.forceGetProperty(currObj, propName);
			if (field==null){
				propClz=BeanUtils.getDeclaredField(currObj, propName).getDeclaringClass();
				field=propClz.newInstance();
			}
			currObj=field;
		}
		propName=propNames[propNames.length-1];
		propClz=BeanUtils.getDeclaredField(currObj, propName).getDeclaringClass();
		Object fieldValue=castType(propClz, formattedValue);
		BeanUtils.forceSetProperty(currObj, propName, fieldValue);
	}

	private Object castType(Class<?> type,String formattedValue) {
			if (type==null){
				return formattedValue;
			}
			if (type == String.class){
				return formattedValue;
			}
			if (type == Boolean.class || type == boolean.class){
				return 	Boolean.valueOf(formattedValue);
			}
			if (type == java.sql.Date.class){		
				return new java.sql.Date(DateFormatTool.parseDate(formattedValue).getTime());
			}
			if (type == java.util.Date.class){
				return DateFormatTool.parseDate(formattedValue);
			}
			if (type == Timestamp.class){
				return DateFormatTool.parseTimestamp(formattedValue);
			}
			if (type == Integer.class || type == int.class){
				return Integer.valueOf(formattedValue);
			}
			
			if (type == Long.class || type == long.class){
				return Long.valueOf(formattedValue);
			}
			if (type == Float.class || type == float.class){
				return Float.valueOf(formattedValue);
			}
			if (type == Double.class || type == double.class){
				return Double.valueOf(formattedValue);
			}
			return formattedValue;
	}

	
	@Override
	public void cell(String cellReference, String formattedValue,XSSFComment comment) {
		if ( rowNum < beginRowNum ){
			return;
		}
        if (cellReference == null) {
            cellReference = new CellAddress(rowNum, colNum).formatAsString();
        }
        int thisCol = (new CellReference(cellReference)).getCol();

        //处理数据中间存在空白
        for (int i = colNum+1; i < thisCol-1; i++) {
        	sheetRow.put(i,"");
        }
    	sheetRow.put(thisCol, formattedValue);
        
        colNum = thisCol;
		
	}
	@Override
	public void hyperlinkCell(String arg0, String arg1, String arg2, String arg3, XSSFComment arg4) {
		// TODO Auto-generated method stub
		
	}
	
	/**
	 * 业务对象数据处理接口
	 * 
	 * @author tbr
	 *
	 * @param <T>
	 */
	public interface DataHandler<T> {
		/**
		 * 业务数据处理方法,
		 * @param data
		 */
		void handleData(T data);
		/**
		 * excel解析抛出异常时的处理方法
		 * 
		 * @param exception
		 */
		void handleException(Exception exception);
	}
}

三、使用例子

String filename="..../测试模板文件.xlsx";//用于测试的xlsx文件名
		Map<Integer,String> fieldMap=new HashMap<>();
		String[] fields={"appId","name","appType","authType","signKey","ipList","describe"};
		for(int i=0;i<fields.length;i++){
			fieldMap.put(i, fields[i]);
		}
		ExcelParser.DataHandler<XXXXBizEntity> dataHandler=new ExcelParser.DataHandler<XXXXBizEntity>(){
			public List<XXXXBizEntity> appinfos=new ArrayList<>();
			public void handleData(XXXXBizEntity data){
				appinfos.add(data);
			}
			public void handleException(Exception exception){
				exception.printStackTrace();
			}
			public String toString(){
				return appinfos.toString();
			}
		};
		ExcelParser<XXXXBizEntity> ep=new ExcelParser<XXXXBizEntity>(filename, XXXXBizEntity.class, fieldMap, 1 , dataHandler);
		ep.parse();

四、写在后面

 

1.需要开发者自己实现DataHandler,比如调用数据持久化到数据库的代码。实现数据集解析出来,调用DataHandler,将数据存入数据库中

2.可以修改这个解析类,使得支持一个文件多个sheet解析不同业务数据。

3.要支持超大数据量,处理的思路基本相同。采用边解析边处理的方法,以减少内存和CPU的消耗。