java EasyExcel實現(xiàn)動態(tài)列解析和存表
背景
一個表中的數(shù)據(jù)來源于多個其他系統(tǒng)的導(dǎo)出表,其中的特點就是大多數(shù)的字段都是一樣的(可能導(dǎo)出的表頭不一樣),只有部分少數(shù)字段是每個系統(tǒng)自己獨有的。圍繞這個做一次功能性分析
分析:大多數(shù)字段是一樣的,那么就是實際的表字段,唯一的區(qū)別就是各系統(tǒng)內(nèi)的名字可能不一樣,少數(shù)每個系統(tǒng)獨有的字段,可以歸為動態(tài)字段。
總結(jié):
- 公共字段(翻譯表頭:
@ExcelProperty
可以指定多個表頭(@ExcelProperty(value = {"發(fā)貨數(shù)量", "采購數(shù)量(臺)"})
)) - 動態(tài)字段(需要有每個系統(tǒng)內(nèi)動態(tài)字段的字段名稱和表頭的對應(yīng)關(guān)系,考慮使用字典,供業(yè)務(wù)員配置,后續(xù)如果新添加其他動態(tài)字段直接在字典中配置,無需另行開發(fā))
注意:由于無法控制和預(yù)料固定字段在新接入的系統(tǒng)中的實際表頭,所以如果新接入系統(tǒng)的公共表頭與表字段不一致,需要在 @ExcelProperty(value = {})
中添加新的表頭
效果
字典配置:
數(shù)據(jù)表結(jié)果:
公共字段使用常規(guī)的數(shù)據(jù)庫表字段存儲,動態(tài)字段使用額外列存 JSON
串。
代碼
引入pom坐標(biāo)
<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.1.0</version> </dependency>
創(chuàng)建實體類
public class AgentDeliverOrderImportVo { @ExcelProperty(value = {"訂單編號"}, order = 1) private String deliverNo; @ExcelProperty(value = {"發(fā)貨數(shù)量", "采購數(shù)量(臺)"}, order = 14) @ColumnName(name = {"發(fā)貨數(shù)量", "采購數(shù)量(臺)"}) private Integer deliverCount; /** * 動態(tài)字段(業(yè)務(wù)線編號區(qū)分) */ private String dynamicFields; private Date createTime; private String createBy; }
- 因為存在不確定的列,所以只能使用
EasyExcel
的不創(chuàng)建對象的寫,那么
public String test(MultipartFile file) throws IOException { //假設(shè)從字典中獲取字典值 Map<String, String> dictMap = new HashMap<>(); dictMap.put("項目", "xm"); dictMap.put("嗨一付訂單編號", "hyfddbh"); try (InputStream inputStream = file.getInputStream()) { EasyExcel.read(inputStream, new ReadListener<Map<String, String>>(){ private Map<Integer, String> fieldHead; //獲取表頭 @Override public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) { Map<Integer, String> integerStringMap = ConverterUtils.convertToStringMap(headMap, context); log.info("解析到一條頭數(shù)據(jù):{}", JSON.toJSONString(integerStringMap)); fieldHead = ExcelParsing.setFieldHead(integerStringMap, AgentDeliverOrderImportVo.class); log.info("轉(zhuǎn)化后頭數(shù)據(jù):{}", JSONObject.toJSONString(fieldHead)); } //獲取數(shù)據(jù) @Override public void invoke(Map<String, String> map, AnalysisContext analysisContext) { log.info("解析到一條數(shù)據(jù):{}", JSON.toJSONString(map)); Map<String, String> valueMap = ExcelParsing.setFieldValue(fieldHead, dictMap, map); log.info("轉(zhuǎn)化一條數(shù)據(jù):{}", JSONObject.toJSONString(valueMap)); log.info("轉(zhuǎn)化一條動態(tài)數(shù)據(jù):{}", JSONObject.toJSONString(ExcelParsing.getValueMap(valueMap, AgentDeliverOrderImportVo.class))); } @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { } }).sheet().doRead(); } return "完成"; } /** * @author Surpass * @Description: excel處理類 * @date 27/07/2022 15:04 */ class ExcelParsing { /** * 將公共字段中的中文轉(zhuǎn)換成數(shù)據(jù)庫表字段,動態(tài)字段(其他字段保留) * @param headMap {1:"姓名", 2:"年齡"} * @param obj AgentDeliverOrderImportVo(導(dǎo)入實體類) * @return java.util.Map<java.lang.String, java.lang.String> {1:"name", 2:"年齡"} * @author Surpass * @date 01/08/2022 17:10 */ public static Map<Integer, String> setFieldHead(Map<Integer, String> headMap, Class<?> obj) { Field[] fields = obj.getDeclaredFields(); for (Field field : fields) { ExcelProperty annotation = field.getAnnotation(ExcelProperty.class); if (annotation == null) { continue; } //存在翻譯字段的情況,一個字段對應(yīng)好幾個表頭(盡量避免) List<String> valueList = Arrays.asList(annotation.value()); for (Map.Entry<Integer, String> entry : headMap.entrySet()) { if (valueList.contains(entry.getValue())) { headMap.put(entry.getKey(), field.getName()); } } } return headMap; } /** * 獲取數(shù)據(jù)(平鋪),指動態(tài)字段kv和公共字段kv在同一級 * @param headMap {1:"name", 2:"年齡"} * @param dictMap {"年齡":"age"} * @param valueMap {1:"廣州****公司", 2:"23"} * @return java.util.Map<java.lang.String, java.lang.String> * @author Surpass * @date 01/08/2022 17:10 */ public static Map<String, String> setFieldValue(Map<Integer, String> headMap, Map<String, String> dictMap, Map<String, String> valueMap) { Map<Integer, String> valueIntegerMap = valueMap.entrySet().stream().collect( Collectors.toMap(item -> Integer.valueOf(String.valueOf(item.getKey())), item -> StrUtil.nullToEmpty(item.getValue())) ); Map<String, String> valueResultMap = new HashMap<>(valueMap.size()); Iterator<Map.Entry<Integer, String>> iterator = valueIntegerMap.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<Integer, String> entry = iterator.next(); //動態(tài)字段 if (dictMap != null && dictMap.containsKey(headMap.get(entry.getKey()))) { valueResultMap.put(dictMap.get(headMap.get(entry.getKey())), entry.getValue()); continue; } //公共字段 valueResultMap.put(headMap.get(entry.getKey()), entry.getValue()); iterator.remove(); } return valueResultMap; } /** * 獲取數(shù)據(jù)(表結(jié)構(gòu)),指動態(tài)字段kv已經(jīng)加入到數(shù)據(jù)庫表字段 dynamicFields 中 * @param obj AgentDeliverOrderImportVo(導(dǎo)入實體類) * @param valueMap {"name":"廣州****公司", "age":"23"} * @return java.util.Map<java.lang.String, java.lang.String> * 返回結(jié)果: {"name":"廣州****公司","dynamicFields":{"age":"23"}} * @author Surpass * @date 01/08/2022 17:10 */ public static Map<String, Object> getValueMap(Map<String, String> valueMap, Class<?> obj) { Map<String, Object> resultMap = new HashMap<>(valueMap); List<String> commonFieldList = new ArrayList<>(); Field[] fields = obj.getDeclaredFields(); for (Field field : fields) { ExcelProperty annotation = field.getAnnotation(ExcelProperty.class); if (annotation == null) { continue; } commonFieldList.add(field.getName()); } //過濾掉實體中的公共字段 Map<String, String> dynamicMap = valueMap.entrySet().stream() .filter(item -> !commonFieldList.contains(item.getKey())) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); resultMap.put("dynamicFields", dynamicMap);; return resultMap; } }
經(jīng)過解析以后這個文檔的數(shù)據(jù)已經(jīng)和數(shù)據(jù)庫表一致了,那么我們后續(xù)的操作就是常規(guī)的校驗和插入邏輯了。
目前有一個缺點就是這樣存的動態(tài)字段不好做條件查詢,影響不是很大。
總結(jié)
本文介紹了使用 EasyExcel
組件來進行導(dǎo)入,實現(xiàn)公共列和動態(tài)列組合類型的導(dǎo)入,以及如何存儲的功能,主要利用反射和字典分別來維護公共列和動態(tài)列的表頭和字段的對應(yīng)關(guān)系,利用此關(guān)系對數(shù)據(jù)進行解析。
以上就是java EasyExcel實現(xiàn)動態(tài)列解析和存表的詳細內(nèi)容,更多關(guān)于java EasyExcel動態(tài)列存表的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring中的NamespaceHandler接口及相關(guān)軟件包說明
這篇文章主要介紹了Spring中的NamespaceHandler接口及相關(guān)軟件包說明,NamespaceHandler 接口,DefaultBeanDefinitionDocumentReader 使用該接口來處理在spring xml 配置文件中自定義的命名空間,需要的朋友可以參考下2023-12-12SpringBoot項目導(dǎo)入aliyun oss starter依賴后啟動報錯問題
這篇文章主要介紹了SpringBoot項目導(dǎo)入aliyun oss starter依賴后啟動報錯問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01Java基礎(chǔ)學(xué)習(xí)之構(gòu)造方法詳解
這篇文章主要為大家詳細介紹了Java基礎(chǔ)學(xué)習(xí)中構(gòu)造方法的概述及注意事項,文中的示例代碼講解詳細,對我們學(xué)習(xí)Java有一定幫助,需要的可以參考一下2022-08-08關(guān)于Java中的實體類要?implements?Serializable的原因分析
這篇文章主要介紹了Java中的實體類為什么要?implements?Serializable,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-06-06springboot整合redis修改分區(qū)的操作流程
這篇文章主要介紹了springboot整合redis修改分區(qū)的操作流程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07