easypoi list中的map导出_如何优雅的导出 Excel
前言
公司項目最近有一個需要:報表導出。整個系統下來,起碼超過一百張報表需要導出。這個時候如何優雅的實現報表導出,釋放生產力就顯得很重要了。下面主要給大家分享一下該工具類的使用方法與實現思路。
實現的功能點
對于每個報表都相同的操作,我們很自然的會抽離出來,這個很簡單。而最重要的是:如何把那些每個報表不相同的操作進行良好的封裝,盡可能的提高復用性;針對以上的原則,主要實現了一下關鍵功能點:
- 導出任意類型的數據
- 自由設置表頭
- 自由設置字段的導出格式
使用實例
上面說到了本工具類實現了三個功能點,自然在使用的時候設置好這三個要點即可:
- 設置數據列表
- 設置表頭
- 設置字段格式
下面的export函數可以直接向客戶端返回一個excel數據,其中productInfoPos為待導出的數據列表,ExcelHeaderInfo用來保存表頭信息,包括表頭名稱,表頭的首列,尾列,首行,尾行。因為默認導出的數據格式都是字符串型,所以還需要一個Map參數用來指定某個字段的格式化類型(例如數字類型,小數類型、日期類型)。這里大家知道個大概怎么使用就好了,下面會對這些參數進行詳細解釋。
實現效果
源碼分析
哈哈,自己分析自己的代碼,有點意思。由于不方便貼出太多的代碼,大家可以先到github上clone源碼,再回來閱讀文章。?源碼地址?LZ使用的poi 4.0.1版本的這個工具,想要實用海量數據的導出自然得使用SXSSFWorkbook這個組件。關于poi的具體用法在這里我就不多說了,這里主要是給大家講解如何對poi進行封裝使用。
成員變量
我們重點看ExcelUtils這個類,這個類是實現導出的核心,先來看一下三個成員變量。
private List list; private List excelHeaderInfos; private Map formatInfo;list
該成員變量用來保存待導出的數據。
ExcelHeaderInfo
該成員變量主要用來保存表頭信息,因為我們需要定義多個表頭信息,所以需要使用一個列表來保存,ExcelHeaderInfo構造函數如下ExcelHeaderInfo(int firstRow, int lastRow, int firstCol, int lastCol, String title)
- firstRow:該表頭所占位置的首行
- lastRow:該表頭所占位置的尾行
- firstCol:該表頭所占位置的首列
- lastCol:該表頭所占位置的尾行
- title:該表頭的名稱
ExcelFormat
該參數主要用來格式化字段,我們需要預先約定好轉換成那種格式,不能隨用戶自己定。所以我們定義了一個枚舉類型的變量,該枚舉類只有一個字符串類型成員變量,用來保存想要轉換的格式,例如FORMAT_INTEGER就是轉換成整型。因為我們需要接受多個字段的轉換格式,所以定義了一個Map類型來接收,該參數可以省略(默認格式為字符串)。
public enum ExcelFormat { FORMAT_INTEGER("INTEGER"), FORMAT_DOUBLE("DOUBLE"), FORMAT_PERCENT("PERCENT"), FORMAT_DATE("DATE"); private String value; ExcelFormat(String value) { this.value = value; } public String getValue() { return value; }}核心方法
1. 創建表頭
該方法用來初始化表頭,而創建表頭最關鍵的就是poi中Sheet類的addMergedRegion(CellRangeAddress var1)方法,該方法用于單元格融合。我們會遍歷ExcelHeaderInfo列表,按照每個ExcelHeaderInfo的坐標信息進行單元格融合,然后在融合之后的每個單元首行和首列的位置創建單元格,然后為單元格賦值即可,通過上面的步驟就完成了任意類型的表頭設置。2. 轉換數據
在進行正文賦值之前,我們先要對原始數據列表轉換成字符串的二維數組,之所以轉成字符串格式是因為可以統一的處理各種類型,之后有需要我們再轉換回來即可。
這個方法中我們通過使用反射技術,很巧妙的實現了任意類型的數據導出(這里的任意類型指的是任意的報表類型,不同的報表,導出的數據肯定是不一樣的,那么在Java實現中的實體類肯定也是不一樣的)。要想將一個List轉換成相應的二維數組,我們得知道如下的信息:
- 二維數組的列數
- 二維數組的行數
- 二維數組每個元素的值
如果獲取以上三個信息呢?
- 通過反射中的Field[] getDeclaredFields()這個方法獲取實體類的所有字段,從而間接知道一共有多少列
- List的大小不就是二維數組的行數了嘛
- 雖然每個實體類的字段名不一樣,那么我們就真的無法獲取到實體類某個字段的值了嗎?不是的,你要知道,你擁有了反射,你就相當于擁有了全世界,那還有什么做不到的呢。這里我們沒有直接使用反射,而是使用了一個叫做BeanUtils的工具,該工具可以很方便的幫助我們對一個實體類進行字段的賦值與字段值的獲取。很簡單,通過BeanUtils.getProperty(list.get(i), columnNames.get(j))這一行代碼,我們就獲取了實體list.get(i)中名稱為columnNames.get(j)這個字段的值。list.get(i)當然是我們遍歷原始數據的實體類,而columnNames列表則是一個實體類所有字段名的數組,也是通過反射的方法獲取到的,具體實現可以參考LZ的源代碼。
3. 賦值正文
這里的正文指定是正式的表格數據內容,其實這一些沒有太多的奇淫技巧,主要的功能在上面已經實現了,這里主要是進行單元格的賦值與導出格式的處理(主要是為了導出excel后可以進行方便的運算)。
導出工具類的核心方法就差不多說完了,下面說一下關于多線程查詢的問題。
多扯兩點
1. 多線程查詢數據
理想很豐滿,現實還是有點骨感的。LZ雖然對50w的數據分別創建20個線程去查詢,但是總體的效率并不是50w/20,而是僅僅快了幾秒鐘,知道原因的小伙伴可以給我留個言一起探討一下。
下面先說說具體思路:因為多個線程之間是同時執行的,你不能夠保證哪個線程先執行完畢,但是我們卻得保證數據順序的一致性。在這里我們使用了Callable接口,通過實現Callable接口的線程可以擁有返回值,我們獲取到所有子線程的查詢結果,然后合并到一個結果集中即可。那么如何保證合并的順序呢?我們先創建了一個FutureTask類型的List,該FutureTask的類型就是返回的結果集。
List>> tasks = new ArrayList<>();當我們每啟動一個線程的時候,就將該線程的FutureTask添加到tasks列表中,這樣tasks列表中的元素順序就是我們啟動線程的順序。
FutureTask> task = new FutureTask<>(new listThread(map)); log.info("開始查詢第{}條開始的{}條記錄總結
以上是生活随笔為你收集整理的easypoi list中的map导出_如何优雅的导出 Excel的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iOS里面MVC模式详解
- 下一篇: 二维数据的白化处理