diff --git a/quickstart.md b/quickstart.md index 7098784c..2087ffca 100644 --- a/quickstart.md +++ b/quickstart.md @@ -7,6 +7,12 @@ * 单个文件的并发写入、读取 * 读取图片 * 宏 +#### 关于常见类解析 +* EasyExcel 入口类,用于构建开始各种操作 +* ExcelReaderBuilder ExcelWriterBuilder 构建出一个 ReadWorkbook WriteWorkbook,可以理解成一个excel对象,一个excel只要构建一个 +* ExcelReaderSheetBuilder ExcelWriterSheetBuilder 构建出一个 ReadSheet WriteSheet对象,可以理解成excel里面的一页,每一页都要构建一个 +* ReadListener 在每一行读取完毕后都会调用ReadListener来处理数据 +* WriteHandler 在每一个操作包括创建单元格、创建表格等都会调用WriteHandler来处理数据 #### 开源项目不容易,如果觉得本项目对您的工作还是有帮助的话,请在右上角帮忙点个★Star。 ### 读 DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/java/com/alibaba/easyexcel/demo/read/ReadTest.java](/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java) @@ -16,7 +22,10 @@ DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/ja * [日期、数字或者自定义格式转换](#converterRead) * [多行头](#complexHeaderRead) * [同步的返回](#synchronousRead) +* [读取表头数据](#synchronousRead) +* [数据转换等异常处理](#exceptionRead) * [web中的读](#webRead) + ### 写 DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java](/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java) * [最简单的写](#simpleWrite) @@ -331,6 +340,88 @@ public class CustomStringStringConverter implements Converter { } ``` +### 同步的返回 +##### excel示例 +参照:[excel示例](#simpleReadExcel) +##### 对象 +参照:[对象](#simpleReadObject) +##### 监听器 +参照:[监听器](#simpleReadListener) +里面多了一个方法,只要重写invokeHeadMap方法即可 +```java + /** + * 这里会一行行的返回头 + * + * @param headMap + * @param context + */ + @Override + public void invokeHeadMap(Map headMap, AnalysisContext context) { + LOGGER.info("解析到一条头数据:{}", JSON.toJSONString(headMap)); + } +``` +##### 代码 +```java + /** + * 读取表头数据 + * + *

+ * 1. 创建excel对应的实体对象 参照{@link DemoData} + *

+ * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener} + *

+ * 3. 直接读即可 + */ + @Test + public void headerRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet 然后千万别忘记 finish + EasyExcel.read(fileName, DemoData.class, new DemoHeadDataListener()).sheet().doRead(); + } +``` + +### 同步的返回 +##### excel示例 +参照:[excel示例](#simpleReadExcel) +##### 对象 +参照:[对象](#simpleReadObject) +##### 监听器 +参照:[监听器](#simpleReadListener) +里面多了一个方法,只要重写onException方法即可 +```java + /** + * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。 + * + * @param exception + * @param context + * @throws Exception + */ + @Override + public void onException(Exception exception, AnalysisContext context) { + LOGGER.error("解析失败,但是继续解析下一行", exception); + } +``` +##### 代码 +```java + /** + * 数据转换等异常处理 + * + *

+ * 1. 创建excel对应的实体对象 参照{@link DemoData} + *

+ * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener} + *

+ * 3. 直接读即可 + */ + @Test + public void exceptionRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet 然后千万别忘记 finish + EasyExcel.read(fileName, DemoData.class, new DemoHeadDataListener()).sheet().doRead(); + } +``` + + ### web中的读 ##### 示例代码 DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java](/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java) diff --git a/src/main/java/com/alibaba/excel/event/AnalysisEventListener.java b/src/main/java/com/alibaba/excel/event/AnalysisEventListener.java index 4d596db9..89bca753 100644 --- a/src/main/java/com/alibaba/excel/event/AnalysisEventListener.java +++ b/src/main/java/com/alibaba/excel/event/AnalysisEventListener.java @@ -1,7 +1,11 @@ package com.alibaba.excel.event; +import java.util.Map; + import com.alibaba.excel.context.AnalysisContext; +import com.alibaba.excel.metadata.CellData; import com.alibaba.excel.read.listener.ReadListener; +import com.alibaba.excel.util.ConverterUtils; /** * Receives the return of each piece of data parsed @@ -10,6 +14,19 @@ import com.alibaba.excel.read.listener.ReadListener; */ public abstract class AnalysisEventListener implements ReadListener { + @Override + public void invokeHead(Map headMap, AnalysisContext context) { + invokeHeadMap(ConverterUtils.convertToStringMap(headMap, context.currentReadHolder()), context); + } + + /** + * Returns the header as a map.Override the current method to receive header data. + * + * @param headMap + * @param context + */ + public void invokeHeadMap(Map headMap, AnalysisContext context) {} + /** * All listeners receive this method when any one Listener does an error report. If an exception is thrown here, the * entire read will terminate. diff --git a/src/main/java/com/alibaba/excel/read/listener/ModelBuildEventListener.java b/src/main/java/com/alibaba/excel/read/listener/ModelBuildEventListener.java index bf4a8cf7..522369ee 100644 --- a/src/main/java/com/alibaba/excel/read/listener/ModelBuildEventListener.java +++ b/src/main/java/com/alibaba/excel/read/listener/ModelBuildEventListener.java @@ -18,6 +18,7 @@ import com.alibaba.excel.metadata.Head; import com.alibaba.excel.metadata.property.ExcelContentProperty; import com.alibaba.excel.read.metadata.holder.ReadHolder; import com.alibaba.excel.read.metadata.property.ExcelReadHeadProperty; +import com.alibaba.excel.util.ConverterUtils; import net.sf.cglib.beans.BeanMap; @@ -28,6 +29,9 @@ import net.sf.cglib.beans.BeanMap; */ public class ModelBuildEventListener extends AbstractIgnoreExceptionReadListener> { + @Override + public void invokeHead(Map cellDataMap, AnalysisContext context) {} + @Override public void invoke(Map cellDataMap, AnalysisContext context) { ReadHolder currentReadHolder = context.currentReadHolder(); @@ -48,7 +52,7 @@ public class ModelBuildEventListener extends AbstractIgnoreExceptionReadListener map.put(entry.getKey(), null); continue; } - map.put(entry.getKey(), (String)convertValue(cellData, String.class, null, + map.put(entry.getKey(), (String)ConverterUtils.convertToJavaObject(cellData, String.class, null, currentReadHolder.converterMap(), currentReadHolder.globalConfiguration())); } return map; @@ -61,8 +65,8 @@ public class ModelBuildEventListener extends AbstractIgnoreExceptionReadListener list.add(null); continue; } - list.add((String)convertValue(cellData, String.class, null, currentReadHolder.converterMap(), - currentReadHolder.globalConfiguration())); + list.add((String)ConverterUtils.convertToJavaObject(cellData, String.class, null, + currentReadHolder.converterMap(), currentReadHolder.globalConfiguration())); } return list; } @@ -90,8 +94,8 @@ public class ModelBuildEventListener extends AbstractIgnoreExceptionReadListener continue; } ExcelContentProperty excelContentProperty = contentPropertyMap.get(index); - Object value = convertValue(cellData, excelContentProperty.getField().getType(), excelContentProperty, - currentReadHolder.converterMap(), currentReadHolder.globalConfiguration()); + Object value = ConverterUtils.convertToJavaObject(cellData, excelContentProperty.getField().getType(), + excelContentProperty, currentReadHolder.converterMap(), currentReadHolder.globalConfiguration()); if (value != null) { map.put(excelContentProperty.getField().getName(), value); } @@ -100,29 +104,6 @@ public class ModelBuildEventListener extends AbstractIgnoreExceptionReadListener return resultModel; } - private Object convertValue(CellData cellData, Class clazz, ExcelContentProperty contentProperty, - Map converterMap, GlobalConfiguration globalConfiguration) { - if (clazz == CellData.class) { - return new CellData(cellData); - } - Converter converter = null; - if (contentProperty != null) { - converter = contentProperty.getConverter(); - } - if (converter == null) { - converter = converterMap.get(ConverterKeyBuild.buildKey(clazz, cellData.getType())); - } - if (converter == null) { - throw new ExcelDataConvertException( - "Converter not found, convert " + cellData.getType() + " to " + clazz.getName()); - } - try { - return converter.convertToJavaData(cellData, contentProperty, globalConfiguration); - } catch (Exception e) { - throw new ExcelDataConvertException("Convert data " + cellData + " to " + clazz + " error ", e); - } - } - @Override public void doAfterAllAnalysed(AnalysisContext context) {} } diff --git a/src/main/java/com/alibaba/excel/read/listener/ReadListener.java b/src/main/java/com/alibaba/excel/read/listener/ReadListener.java index 09ce3c78..142c531b 100644 --- a/src/main/java/com/alibaba/excel/read/listener/ReadListener.java +++ b/src/main/java/com/alibaba/excel/read/listener/ReadListener.java @@ -1,7 +1,10 @@ package com.alibaba.excel.read.listener; +import java.util.Map; + import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.Listener; +import com.alibaba.excel.metadata.CellData; /** * Interface to listen for read results @@ -20,7 +23,15 @@ public interface ReadListener extends Listener { void onException(Exception exception, AnalysisContext context) throws Exception; /** - * when analysis one row trigger invoke function. + * When analysis one head row trigger invoke function. + * + * @param headMap + * @param context + */ + void invokeHead(Map headMap, AnalysisContext context); + + /** + * When analysis one row trigger invoke function. * * @param data * one row value. Is is same as {@link AnalysisContext#readRowHolder()} diff --git a/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java b/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java index 61aa8137..4e7041e6 100644 --- a/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java +++ b/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java @@ -26,6 +26,7 @@ import com.alibaba.excel.read.listener.ReadListenerRegistryCenter; import com.alibaba.excel.read.listener.event.AnalysisFinishEvent; import com.alibaba.excel.read.metadata.ReadBasicParameter; import com.alibaba.excel.read.metadata.property.ExcelReadHeadProperty; +import com.alibaba.excel.util.ConverterUtils; import com.alibaba.excel.util.StringUtils; /** @@ -118,8 +119,11 @@ public abstract class AbstractReadHolder extends AbstractHolder implements ReadH Map cellDataMap = event.getAnalysisResult(); ReadRowHolder readRowHolder = analysisContext.readRowHolder(); readRowHolder.setCurrentRowAnalysisResult(cellDataMap); + int rowIndex = readRowHolder.getRowIndex(); + int headRowNumber = analysisContext.readSheetHolder().getHeadRowNumber(); - if (readRowHolder.getRowIndex() >= analysisContext.readSheetHolder().getHeadRowNumber()) { + if (rowIndex >= headRowNumber) { + // Now is data for (ReadListener readListener : analysisContext.currentReadHolder().readListenerList()) { try { readListener.invoke(readRowHolder.getCurrentRowAnalysisResult(), analysisContext); @@ -136,11 +140,29 @@ public abstract class AbstractReadHolder extends AbstractHolder implements ReadH throw new ExcelAnalysisStopException(); } } - return; - } - // Now is header - if (analysisContext.readSheetHolder().getHeadRowNumber().equals(readRowHolder.getRowIndex() + 1)) { - buildHead(analysisContext, cellDataMap); + } else { + // Last head column + if (headRowNumber == rowIndex + 1) { + buildHead(analysisContext, cellDataMap); + } + + // Now is header + for (ReadListener readListener : analysisContext.currentReadHolder().readListenerList()) { + try { + readListener.invokeHead(cellDataMap, analysisContext); + } catch (Exception e) { + for (ReadListener readListenerException : analysisContext.currentReadHolder().readListenerList()) { + try { + readListenerException.onException(e, analysisContext); + } catch (Exception exception) { + throw new ExcelAnalysisException("Listen error!", exception); + } + } + } + if (!readListener.hasNext(analysisContext)) { + throw new ExcelAnalysisStopException(); + } + } } } @@ -155,7 +177,8 @@ public abstract class AbstractReadHolder extends AbstractHolder implements ReadH if (!HeadKindEnum.CLASS.equals(analysisContext.currentReadHolder().excelReadHeadProperty().getHeadKind())) { return; } - Map dataMap = buildStringMap(cellDataMap, analysisContext.currentReadHolder()); + Map dataMap = + ConverterUtils.convertToStringMap(cellDataMap, analysisContext.currentReadHolder()); ExcelReadHeadProperty excelHeadPropertyData = analysisContext.readSheetHolder().excelReadHeadProperty(); Map headMapData = excelHeadPropertyData.getHeadMap(); Map contentPropertyMapData = excelHeadPropertyData.getContentPropertyMap(); @@ -192,30 +215,6 @@ public abstract class AbstractReadHolder extends AbstractHolder implements ReadH excelHeadPropertyData.setContentPropertyMap(tmpContentPropertyMap); } - private Map buildStringMap(Map cellDataMap, ReadHolder readHolder) { - Map stringMap = new HashMap(cellDataMap.size() * 4 / 3 + 1); - for (Map.Entry entry : cellDataMap.entrySet()) { - CellData cellData = entry.getValue(); - if (cellData.getType() == CellDataTypeEnum.EMPTY) { - stringMap.put(entry.getKey(), null); - continue; - } - Converter converter = - readHolder.converterMap().get(ConverterKeyBuild.buildKey(String.class, cellData.getType())); - if (converter == null) { - throw new ExcelDataConvertException( - "Converter not found, convert " + cellData.getType() + " to String"); - } - try { - stringMap.put(entry.getKey(), - (String)(converter.convertToJavaData(cellData, null, readHolder.globalConfiguration()))); - } catch (Exception e) { - throw new ExcelDataConvertException("Convert data " + cellData + " to String error ", e); - } - } - return stringMap; - } - public List getReadListenerList() { return readListenerList; } diff --git a/src/main/java/com/alibaba/excel/util/ConverterUtils.java b/src/main/java/com/alibaba/excel/util/ConverterUtils.java new file mode 100644 index 00000000..7445b4ce --- /dev/null +++ b/src/main/java/com/alibaba/excel/util/ConverterUtils.java @@ -0,0 +1,87 @@ +package com.alibaba.excel.util; + +import java.util.HashMap; +import java.util.Map; + +import com.alibaba.excel.converters.Converter; +import com.alibaba.excel.converters.ConverterKeyBuild; +import com.alibaba.excel.enums.CellDataTypeEnum; +import com.alibaba.excel.exception.ExcelDataConvertException; +import com.alibaba.excel.metadata.CellData; +import com.alibaba.excel.metadata.GlobalConfiguration; +import com.alibaba.excel.metadata.property.ExcelContentProperty; +import com.alibaba.excel.read.metadata.holder.ReadHolder; + +/** + * Converting objects + * + * @author Jiaju Zhuang + **/ +public class ConverterUtils { + + private ConverterUtils() {} + + /** + * Convert it into a String map + * + * @param cellDataMap + * @param readHolder + * @return + */ + public static Map convertToStringMap(Map cellDataMap, ReadHolder readHolder) { + Map stringMap = new HashMap(cellDataMap.size() * 4 / 3 + 1); + for (Map.Entry entry : cellDataMap.entrySet()) { + CellData cellData = entry.getValue(); + if (cellData.getType() == CellDataTypeEnum.EMPTY) { + stringMap.put(entry.getKey(), null); + continue; + } + Converter converter = + readHolder.converterMap().get(ConverterKeyBuild.buildKey(String.class, cellData.getType())); + if (converter == null) { + throw new ExcelDataConvertException( + "Converter not found, convert " + cellData.getType() + " to String"); + } + try { + stringMap.put(entry.getKey(), + (String)(converter.convertToJavaData(cellData, null, readHolder.globalConfiguration()))); + } catch (Exception e) { + throw new ExcelDataConvertException("Convert data " + cellData + " to String error ", e); + } + } + return stringMap; + } + + /** + * Convert it into a Java object + * + * @param cellData + * @param clazz + * @param contentProperty + * @param converterMap + * @param globalConfiguration + * @return + */ + public static Object convertToJavaObject(CellData cellData, Class clazz, ExcelContentProperty contentProperty, + Map converterMap, GlobalConfiguration globalConfiguration) { + if (clazz == CellData.class) { + return new CellData(cellData); + } + Converter converter = null; + if (contentProperty != null) { + converter = contentProperty.getConverter(); + } + if (converter == null) { + converter = converterMap.get(ConverterKeyBuild.buildKey(clazz, cellData.getType())); + } + if (converter == null) { + throw new ExcelDataConvertException( + "Converter not found, convert " + cellData.getType() + " to " + clazz.getName()); + } + try { + return converter.convertToJavaData(cellData, contentProperty, globalConfiguration); + } catch (Exception e) { + throw new ExcelDataConvertException("Convert data " + cellData + " to " + clazz + " error ", e); + } + } +} diff --git a/src/test/java/com/alibaba/easyexcel/test/core/simple/SimpleDataListener.java b/src/test/java/com/alibaba/easyexcel/test/core/simple/SimpleDataListener.java index 757cb07a..b8925f81 100644 --- a/src/test/java/com/alibaba/easyexcel/test/core/simple/SimpleDataListener.java +++ b/src/test/java/com/alibaba/easyexcel/test/core/simple/SimpleDataListener.java @@ -2,6 +2,7 @@ package com.alibaba.easyexcel.test.core.simple; import java.util.ArrayList; import java.util.List; +import java.util.Map; import org.junit.Assert; import org.slf4j.Logger; @@ -18,6 +19,12 @@ public class SimpleDataListener extends AnalysisEventListener { private static final Logger LOGGER = LoggerFactory.getLogger(SimpleDataListener.class); List list = new ArrayList(); + @Override + public void invokeHeadMap(Map headMap, AnalysisContext context) { + LOGGER.debug("Head is:{}", JSON.toJSONString(headMap)); + Assert.assertEquals(headMap.get(0), "姓名"); + } + @Override public void invoke(SimpleData data, AnalysisContext context) { list.add(data); diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoHeadDataListener.java b/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoHeadDataListener.java new file mode 100644 index 00000000..bc55052b --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoHeadDataListener.java @@ -0,0 +1,73 @@ +package com.alibaba.easyexcel.test.demo.read; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.junit.Assert; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.excel.context.AnalysisContext; +import com.alibaba.excel.event.AnalysisEventListener; +import com.alibaba.fastjson.JSON; + +/** + * 读取头 + * + * @author Jiaju Zhuang + */ +public class DemoHeadDataListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(DemoHeadDataListener.class); + /** + * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收 + */ + private static final int BATCH_COUNT = 5; + List list = new ArrayList(); + + /** + * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。 + * + * @param exception + * @param context + * @throws Exception + */ + @Override + public void onException(Exception exception, AnalysisContext context) { + LOGGER.error("解析失败,但是继续解析下一行", exception); + } + + /** + * 这里会一行行的返回头 + * + * @param headMap + * @param context + */ + @Override + public void invokeHeadMap(Map headMap, AnalysisContext context) { + LOGGER.info("解析到一条头数据:{}", JSON.toJSONString(headMap)); + } + + @Override + public void invoke(DemoData data, AnalysisContext context) { + LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data)); + if (list.size() >= BATCH_COUNT) { + saveData(); + list.clear(); + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + saveData(); + LOGGER.info("所有数据解析完成!"); + } + + /** + * 加上存储数据库 + */ + private void saveData() { + LOGGER.info("{}条数据,开始存储数据库!", list.size()); + LOGGER.info("存储数据库成功!"); + } +} diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java index 90524d36..b65f99ea 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java @@ -134,6 +134,40 @@ public class ReadTest { .headRowNumber(1).doRead(); } + /** + * 读取表头数据 + * + *

+ * 1. 创建excel对应的实体对象 参照{@link DemoData} + *

+ * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener} + *

+ * 3. 直接读即可 + */ + @Test + public void headerRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet 然后千万别忘记 finish + EasyExcel.read(fileName, DemoData.class, new DemoHeadDataListener()).sheet().doRead(); + } + + /** + * 数据转换等异常处理 + * + *

+ * 1. 创建excel对应的实体对象 参照{@link DemoData} + *

+ * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener} + *

+ * 3. 直接读即可 + */ + @Test + public void exceptionRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet 然后千万别忘记 finish + EasyExcel.read(fileName, DemoData.class, new DemoHeadDataListener()).sheet().doRead(); + } + /** * 同步的返回,不推荐使用,如果数据量大会把数据放到内存里面 */ diff --git a/update.md b/update.md index f0bf39bb..e9e7d5e5 100644 --- a/update.md +++ b/update.md @@ -1,5 +1,6 @@ # 2.0.0-beta3 * 导出完成移除临时目录 [Issue #386](https://github.com/alibaba/easyexcel/issues/386) +* 新增读取返回头数据 # 2.0.0-beta2 * 加速gc回收 [Issue #511](https://github.com/alibaba/easyexcel/issues/511)