easyExcel嵌套子集合導出Excel

我想要的Excel效果
在這里插入圖片描述
說明:
1.創建兩個自定義注解:@ExcelMerge(表示主對象內的單個屬性,后續會根據子集合的大小合并下面的單元格),@ExcelNestedList(表示嵌套的子集合)
2.NestedDataConverter.java 會把查詢到的數據轉換為一行一行的,相當于主表 left join 子表 ON 主.id=子.主id的形式
SmartMergeStrategy.java 在使用EasyExcel時使用的策略類,會計算每組數據需要合并的row層數
3. public void t3()執行的Main方法.

package xxx.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 標記主實體中需要合并的字段*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelMerge {// 可以添加其他屬性,如合并策略等
}
package xxx.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 標記包含子列表的字段*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelNestedList {Class<?> value(); // 指定子元素的類型
}
package xxx.common;import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;import org.springframework.stereotype.Component;import com.alibaba.excel.annotation.ExcelProperty;import xxx.annotation.ExcelNestedList;public class NestedDataConverter {public static List<List<Object>> convertToNestedListWithNesting(List<?> dataList) {List<List<Object>> result = new ArrayList<>();if (dataList.isEmpty()) {return result;}// Extract parameter names to maintain the orderClass<?> clazz = dataList.get(0).getClass();List<String> parameterNames = extractParameterNames(clazz);for (Object data : dataList) {List<Field> nestedFields = Arrays.stream(clazz.getDeclaredFields()).filter(f -> f.isAnnotationPresent(ExcelNestedList.class)).collect(Collectors.toList());if (nestedFields.isEmpty()) {// No nested fields, convert the object directlyresult.add(convertSingleToList(data, parameterNames));} else {Map<String, Object> fieldValues = new HashMap<>();populateFieldValues(data, fieldValues);for (Field nestedField : nestedFields) {try {nestedField.setAccessible(true);List<?> nestedList = (List<?>) nestedField.get(data);Class<?> nestedClass = nestedField.getAnnotation(ExcelNestedList.class).value();if (nestedList == null || nestedList.isEmpty()) {// Add a row with empty strings for nested fieldsList<Object> row = buildRowFromFieldValues(fieldValues, parameterNames, nestedClass, true);result.add(row);} else {for (Object nestedItem : nestedList) {Map<String, Object> nestedFieldValues = new HashMap<>(fieldValues);populateFieldValues(nestedItem, nestedFieldValues);List<Object> row = buildRowFromFieldValues(nestedFieldValues, parameterNames, nestedClass, false);result.add(row);}}} catch (IllegalAccessException e) {throw new RuntimeException("Failed to access nested data", e);}}}}return result;}private static void populateFieldValues(Object data, Map<String, Object> fieldValues) {for (Field field : data.getClass().getDeclaredFields()) {ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);if (excelProperty != null) {try {field.setAccessible(true);Object value = field.get(data);fieldValues.put(excelProperty.value()[0], value != null ? value : ""); // Replace null with empty string} catch (IllegalAccessException e) {fieldValues.put(excelProperty.value()[0], ""); // Add empty string if inaccessible}}}}private static List<Object> buildRowFromFieldValues(Map<String, Object> fieldValues, List<String> parameterNames, Class<?> nestedClass, boolean isEmptyNested) {List<Object> row = new ArrayList<>();for (String paramName : parameterNames) {if (fieldValues.containsKey(paramName)) {row.add(fieldValues.get(paramName));} else if (isEmptyNested && isNestedField(paramName, nestedClass)) {row.add(""); // Add empty string for empty nested fields}}return row;}private static boolean isNestedField(String paramName, Class<?> nestedClass) {return Arrays.stream(nestedClass.getDeclaredFields()).anyMatch(f -> f.isAnnotationPresent(ExcelProperty.class) && f.getAnnotation(ExcelProperty.class).value()[0].equals(paramName));}private static List<Object> convertSingleToList(Object data, List<String> parameterNames) {List<Object> row = new ArrayList<>();Map<String, Object> fieldValues = new HashMap<>();for (Field field : data.getClass().getDeclaredFields()) {ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);if (excelProperty != null) {try {field.setAccessible(true);fieldValues.put(excelProperty.value()[0], field.get(data));} catch (IllegalAccessException e) {fieldValues.put(excelProperty.value()[0], ""); // Add empty string if inaccessible}}}// Add values in the order of parameterNamesfor (String paramName : parameterNames) {row.add(fieldValues.getOrDefault(paramName, ""));}return row;}public static List<String> extractParameterNames(Class<?> clazz) {List<String> parameterNames = new ArrayList<>();for (Field field : clazz.getDeclaredFields()) {// Check if the field is annotated with @ExcelPropertyExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);// Check if the field is annotated with @ExcelNestedListExcelNestedList nestedList = field.getAnnotation(ExcelNestedList.class);if (excelProperty != null && nestedList == null) {parameterNames.add(excelProperty.value()[0]); // Add the parameter name}// Check if the field is annotated with @ExcelNestedList
//            ExcelNestedList nestedList = field.getAnnotation(ExcelNestedList.class);if (nestedList != null) {// Recursively extract parameter names from the nested classClass<?> nestedClass = nestedList.value();parameterNames.addAll(extractParameterNames(nestedClass));}}return parameterNames;}
}
package xxx.common;import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.merge.AbstractMergeStrategy;import xxx.annotation.ExcelMerge;public class SmartMergeStrategy extends AbstractMergeStrategy {private final List<?> dataList;private final Class<?> clazz;private final Map<Integer, List<int[]>> mergeInfo = new HashMap<>(); public SmartMergeStrategy(List<?> dataList, Class<?> clazz) {this.dataList = dataList;this.clazz = clazz;prepareMergeInfo();}private void prepareMergeInfo() {// 獲取所有帶有@ExcelMerge注釋的字段List<Field> mergeFields = Arrays.stream(clazz.getDeclaredFields()).filter(field -> field.isAnnotationPresent(ExcelMerge.class)).collect(Collectors.toList());int currentRow = 1; // Start from row 1 (after the header)從第 1 行開始(標題之后)for (Object data : dataList) {try {// Get the `id` field value
//                Field idField = clazz.getDeclaredField("id");
//                idField.setAccessible(true);
//                Object idValue = idField.get(data);// 獲取嵌套的 `forwards` 列表
//                Field nestedField = clazz.getDeclaredField("forwards");Field nestedField = Arrays.stream(clazz.getDeclaredFields()).filter(f -> List.class.isAssignableFrom(f.getType())).findFirst().orElseThrow(() -> new RuntimeException("未找到 List 類型字段"));nestedField.setAccessible(true);List<?> nestedList = (List<?>) nestedField.get(data);int nestedSize = (nestedList != null) ? nestedList.size() : 0;int startRow = currentRow;int endRow = (nestedSize > 0) ? (currentRow + nestedSize - 1) : currentRow;// 計算每個“@ExcelMerge”列的合并范圍for (Field field : mergeFields) {int colIndex = getColumnIndex(field);if (colIndex >= 0 && startRow != endRow) {mergeInfo.computeIfAbsent(colIndex, k -> new ArrayList<>()).add(new int[]{startRow, endRow});}}// Update the current row pointer更新當前行指針currentRow = endRow + 1;} catch (IllegalAccessException e) {e.printStackTrace();}}}private int getColumnIndex(Field field) {ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);if (excelProperty != null) {String[] values = excelProperty.value();if (values.length > 0) {String columnName = values[0];List<Field> allFields = Arrays.stream(clazz.getDeclaredFields()).filter(f -> f.isAnnotationPresent(ExcelProperty.class)).collect(Collectors.toList());for (int i = 0; i < allFields.size(); i++) {Field currentField = allFields.get(i);ExcelProperty property = currentField.getAnnotation(ExcelProperty.class);if (property != null && property.value().length > 0 && property.value()[0].equals(columnName)) {return i;}}}}return -1;}@Overrideprotected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {int colIndex = cell.getColumnIndex();if (mergeInfo.containsKey(colIndex)) {List<int[]> ranges = mergeInfo.get(colIndex);for (int[] range : ranges) {int startRow = range[0];int endRow = range[1];if (cell.getRowIndex() == startRow) {CellRangeAddress region = new CellRangeAddress(startRow, endRow, colIndex, colIndex);sheet.addMergedRegion(region);}}}}}
package xxx.entity;import java.util.List;import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;import lombok.Data;
import xxx.annotation.ExcelMerge;
import xxx.annotation.ExcelNestedList;@Data
@TableName(value = "bkdb3.actions")
public class Action {@TableId(type = IdType.AUTO)@ExcelProperty("ID")@ExcelMergeprivate Long id;@ExcelProperty("Struts-Conf")@ExcelMergeprivate String strutsconfName;@ExcelIgnoreprivate String attribute;@ExcelIgnoreprivate String name;@ExcelIgnoreprivate String parameter;@ExcelProperty("Path")@ExcelMergeprivate String path;@ExcelProperty("ActionClassPath")@ExcelMergeprivate String type;// 非數據庫字段,用于關聯 forward 列表@TableField(exist = false)@ExcelProperty("forwards")@ExcelNestedList(Forward.class)private List<Forward> forwards;}
package xxx.entity;import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;import lombok.Data;@Data
@TableName(value = "bkdb3.forwards")
public class Forward {@TableId(type = IdType.AUTO)private Long id;private Long actionId; // 外鍵,關聯 actions 表private String name;@ExcelProperty("forwardPath")private String path;}
/**把Action Forward輸出為以下這種形式| Action ID | Action Name | Action Path | Forward Name | Forward Path || --------- | ----------- | ----------- | ------------ | ------------ || 1         | doLogin     | /login      | success      | /main.jsp    ||           |             |             | error        | /login.jsp   || 2         | doSearch    | /search     | next         | /result.jsp  |**/@Disabled@Testpublic void t3() {QueryWrapper<Action> wrapper = new QueryWrapper<>();wrapper.orderByAsc("id");List<Action> actions = actionMapper.selectList(wrapper);// 第二步:查詢所有 forwards 一次性(推薦,一次查庫,避免N+1)List<Long> actionIds = actions.stream().map(Action::getId).collect(Collectors.toList());if (!actionIds.isEmpty()) {List<Forward> allForwards = forwardMapper.selectList(new QueryWrapper<Forward>().in("action_id", actionIds));// 第三步:將 forwards 按照 actionId 分組Map<Long, List<Forward>> forwardMap = allForwards.stream().collect(Collectors.groupingBy(Forward::getActionId));// 第四步:將 forward 分別賦值到每個 Action 上for (Action action : actions) {List<Forward> childForwards = forwardMap.getOrDefault(action.getId(), new ArrayList<>());action.setForwards(childForwards);}}log.info("actions size={}",actions.size());List<List<Object>> lists = NestedDataConverter.convertToNestedListWithNesting(actions);// 4. 創建并注冊合并策略SmartMergeStrategy mergeStrategy = new SmartMergeStrategy(actions, Action.class);// 獲取所有可能的表頭字段List<String> heads = NestedDataConverter.extractParameterNames(Action.class);// 5. 導出ExcelEasyExcel.write(analyzeProperties.getExcel1(),Action.class).registerWriteHandler(mergeStrategy) // 注冊合并策略
//                .head(createHead(heads)) // 動態生成表頭.sheet("Action").doWrite(lists);log.info("導出歷史記錄成功");}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/93192.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/93192.shtml
英文地址,請注明出處:http://en.pswp.cn/web/93192.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于 C# WinForm 字體編輯器開發記錄:從基礎到進階

目錄 基礎版本實現 進階版本改進 字體設置窗體增強 主窗體改進 功能對比 項目在本文章的綁定資源中免費的&#xff0c;0積分就可以下載哦~ 在 Windows Forms 應用開發中&#xff0c;字體編輯功能是許多文本處理軟件的基礎功能。本文將分享一個簡易字體編輯器的開發過程&a…

Linux基本使用和Java程序部署(含 JDK 與 MySQL)

文章目錄Linux 背景知識Linux 基本使用Linux 常用的特殊符號和操作符Linux 常用命令文本處理與分析系統管理與操作用戶與權限管理文件/目錄操作與內容處理工具Linux系統防火墻Shell 腳本與實踐搭建 Java 部署環境apt&#xff08;Debian/Ubuntu 系的包管理利器&#xff09;介紹安…

抗輻照CANFD通信芯片在高安全領域國產化替代的研究

摘要&#xff1a;隨著現代科技的飛速發展&#xff0c;高安全領域如航空航天、衛星通信等對電子設備的可靠性與抗輻照性能提出了極高的要求。CANFD通信芯片作為數據傳輸的關鍵組件&#xff0c;其性能優劣直接關系到整個系統的穩定性與安全性。本文聚焦于抗輻照CANFD通信芯片在高…

Mybatis 源碼解讀-SqlSession 會話源碼和Executor SQL操作執行器源碼

作者源碼閱讀筆記主要采用金山云文檔記錄的&#xff0c;所有的交互圖和代碼閱讀筆記都是記錄在云文檔里面&#xff0c;本平臺的文檔編輯實在不方便&#xff0c;會導致我梳理的交互圖和文檔失去原來的格式&#xff0c;所以整理在文檔里面&#xff0c;供大家閱讀交流. 【金山文檔…

Java集合類綜合練習題

代碼 import java.util.*; class ScoreRecord {private String studentId;private String name;private String subject;private int score;public ScoreRecord(String studentId, String name, String subject, int score) {this.studentId studentId;this.name name;this.s…

秒懂邊緣云|1分鐘了解邊緣安全加速 ESA

普通開發者如何搭建安全快速的在線業務才能性價比最高 &#xff1f;阿里云現已為開發者推出免費版邊緣安全加速 ESA&#xff0c;1 個產品就能把 CDN 緩存 API 加速 DNS WAF DDoS 防護全部搞定&#xff0c;還支持邊緣函數快速部署網站和 AI 應用&#xff0c;性價比拉滿。 1…

數據結構:串、數組與廣義表

&#x1f4cc;目錄&#x1f524; 一&#xff0c;串的定義&#x1f330; 二&#xff0c;案例引入場景1&#xff1a;文本編輯器中的查找替換場景2&#xff1a;用戶手機號驗證&#x1f4da; 三&#xff0c;串的類型定義、存儲結構及其運算&#xff08;一&#xff09;串的抽象類型定…

服務器路由相關配置Linux和Windows

服務器路由相關配置Linux和Windowscentos路由系統核心概念傳統工具集(命令)iproute2 工具集&#xff08;推薦&#xff09;NetworkManager 工具路由配置文件體系高級路由功能策略路由多路徑路由路由監控工具系統級路由配置啟用IP轉發路由守護進程路由問題診斷流程Windows 路由Wi…

Spring Boot啟動事件詳解:類型、監聽與實戰應用

1. Spring Boot啟動事件概述1.1 什么是Spring Boot啟動事件在Spring Boot的應用生命周期中&#xff0c;從main方法執行到應用完全就緒&#xff0c;期間會發生一系列事件&#xff08;Event&#xff09;。這些事件由Spring Boot框架在特定時間點觸發&#xff0c;用于通知系統當前…

Python閉包詳解:理解閉包與可變類型和不可變類型的關系

一、定義閉包&#xff08;Closure&#xff09; 指的是一個函數對象&#xff0c;即使其外部作用域的變量已經不存在了&#xff0c;仍然能訪問這些變量。簡單來說&#xff0c;閉包是由函數及其相關的環境變量組成的實體。def outer():x 10def inner():print(x)return innerf ou…

BotCash:GPT-5發布觀察 工程優化的進步,還是技術突破的瓶頸?

BotCash&#xff1a;GPT-5發布觀察 工程優化的進步&#xff0c;還是技術突破的瓶頸&#xff1f; 在GPT-4以多模態能力震撼業界的一年后&#xff0c;GPT-5的亮相顯得有些“平靜”。當人們期待著又一場顛覆性技術革命時&#xff0c;這場發布會更像是給大模型技術按下了“精細打磨…

AJAX學習(2)

目錄 一.XMLHttpRequest 二.XMLHttpRequest——查詢參數 三.案例——地區查詢 四.XMLHttpRequest_數據提交 五.Promise 六.Promise三種狀態 七.PromiseeeXHR獲取省份列表&#xff08;案例&#xff09; 八.封裝-簡易axios-獲取省份列表 九.封裝-簡易axios-獲取地區列表 …

解決 pip 安裝包時出現的 ReadTimeoutError 方法 1: 臨時使用鏡像源(單次安裝)

解決 pip 安裝包時出現的 ReadTimeoutError 當您在使用 pip 安裝 Python 包時遇到 pip._vendor.urllib3.exceptions.ReadTimeoutError: HTTPSConnectionPool(hostfiles.pythonhosted.org, port443): Read timed out. 錯誤時&#xff0c;這通常是由于網絡問題導致的連接超時。P…

Linux下使用Samba 客戶端訪問 Samba 服務器的配置(Ubuntu Debian)

在 Linux 系統中&#xff0c;Samba 提供了與 Windows 系統文件共享的便利方式。本文將詳細介紹在 Ubuntu 和 Debian 系統下如何安裝 Samba 客戶端、訪問共享資源&#xff0c;并實現遠程目錄掛載和開機自動掛載。 文章參考自&#xff08;感謝分享&#xff09;&#xff1a;https…

解決dedecms文章默認關鍵字太短的問題

在管理文章或軟件的時候&#xff0c;大家在添加關鍵字和內容摘要的時候&#xff0c;是不是對這樣的情況感到比較的郁悶&#xff0c;我的關鍵字設定的明明非常的好&#xff0c;可是添加或修改后&#xff0c;會被無緣無故的截去很多&#xff0c;想必大家也都非常的明白&#xff0…

K8s-kubernetes(二)資源限制-詳細介紹

K8s如何合理規定對象資源使用 基本概念 Kubernetes中&#xff0c;占用資源的最小單元為單個PodKubernetes中&#xff0c;資源占用主要針對服務器的CPU、內存 為什么要做資源限制 對于Kubernetes集群而言&#xff0c;所有Pod都會占用K8s集群所在服務器的資源&#xff0c;如果不做…

量子神經網絡:從NISQ困境到邏輯比特革命的破局之路

——解析2025千比特時代開發者的機遇與行動框架 引言:量子計算的“20比特魔咒”與千比特悖論 當開發者被建議“避免在>20量子比特電路訓練”時,富士通卻宣布2025年實現10,000物理比特系統。這一矛盾揭示了量子計算從NISQ時代向FTQC時代躍遷的核心邏輯:千比特突破非為直接…

react+vite-plugin-react-router-generator自動化生成路由

前言&#xff1a;react項目實際使用中有很多提升性能與功能的插件&#xff0c;今天來說一說vite里面提供的vite-plugin-react-router-generator&#xff0c;他主要提供了自動生成路由的功能&#xff0c;配合我們的loadable/component可以實現路由的懶加載與統一管理。1、實現效…

服務器查看 GPU 占用情況的方法

在 Linux 系統中查看 GPU 占用情況&#xff0c;主要取決于你的 GPU 類型&#xff08;NVIDIA/AMD&#xff09;&#xff0c;以下是常用方法&#xff1a; 一、NVIDIA GPU&#xff08;最常用&#xff0c;如 RTX 系列、Tesla 系列&#xff09; 使用 NVIDIA 官方工具 nvidia-smi&…

【Docker實戰進階】Docker 實戰命令大全

Docker 實戰命令大全 Docker 實戰場景&#xff0c;以 Nginx 為核心示例&#xff0c;梳理容器生命周期、鏡像管理、網絡配置、數據持久化及 Compose 編排的核心命令與最佳實踐。 一、容器生命周期管理 1. 基礎生命周期命令 docker run - 創建并啟動容器 核心功能&#xff1a;基于…