---
# System prepended metadata

title: '筆記: JAVA反射應用-EXCEL'
tags: [java]

---

# 筆記: JAVA反射應用_EXCEL輸出

工作上需要將資料庫中的資料輸出為EXCEL的樣式
常見的是使用apache poi 套件中的workbook去帶入資料庫資料
在帶入時會用for迴圈將需要的資料填入每一個資料格(cell)
如不用在填入資料時至少要依照欄位的多寡寫出相對數量的code，若有很多欄位這段會寫得很長且日後要修改不方便


*由於重點在反射, 這邊會省略Apache poi的引入方式和簡述該套件應用上的說明
*下面的範例也能抽出寫成共用方法，如有excel固定需求可以一併寫入

程式上會建立org.apache.poi.ss.usermodel.**Workbook**的實作類別
有兩種格式**HSSF**和**XSSF**，分別對應副檔名xls和xlsx
這邊使用XSSFWorkbook


Sheet: 工作表
Row: 行
Cell:格

普遍格式如下, 第一行為標題行之後均為資料行
有其他不同的形式請再根據實際情況做變化

| 欄1| 欄2 | 欄3 | 
| -------- | -------- | -------- |
| Cell     | Cell     | Cell     |
| Cell      | Cell     | Cell     |

呈現結果類似下圖，單純調過欄寬, 其他一些格式調整可以在程式裡控制
![image](https://hackmd.io/_uploads/rkkrodeo6.png)



---

## 帶入資料庫資料的前置工作
```
// ...前略資料查詢的部分程式
Workbook workbook = new XSSFWorkbook();

// 建立工作表
Sheet sheet = workbook.createSheet("訂貨清單");

// 設置所需的excel格式設定
sheet.setDefaultColumnWidth(30);

```

接著需要準備一個定義標題欄的資料當你需要在標頭顯示欄位名稱,
這邊個人使用LinkedHashMap做為設定excel header的容器
key對應資料物件的屬性名;value設定為欄位名稱

也可以使用其他方式作定義, 如用物件或從DB提取相關的設定, 端看設計方式和需求

```
Map<String, String> headerTitleMap = new LinkedHashMap<String, String>();
headerTitleMap.put("field1", "產品類別");
headerTitleMap.put("field2", "業務員編號/姓名");
headerTitleMap.put("field3", "客戶名稱");
headerTitleMap.put("field4", "訂貨時間");
headerTitleMap.put("field5", "狀態");
```


進入填充excel資料的部分
這邊第一層迴圈使用的queryList為查詢的資料，因為每筆資料都相當於每一行
但注意第一行為(i=0)標題行, 資料行的處理從(i=1)開始

*做excel處理基本上就要用到兩層迴圈了(除非固定工作表)，再加上使用反射去處理cell塞資料會有一些clean code上的問題建議要再處理過
大概會如下的結構
```
for(int i==0;資料集合 >=i;i++){
    for(Entry<String, String> title: headerTitleMap.entrySet()){
    
    }
}
```

---


現在我們專注在有使用到反射的部分
我們將儲存資料的物件dataObj
使用Class的反射方法getDeclaredFields()去得到Field[];
```
Field[] fields = dataObj.getClass().getDeclaredFields();
```
這個Field陣列內會有當下這個dataObj物件各個屬性的相關資料,之後將這個Field[]的陣列用迴圈取出Filed物件
![image](https://hackmd.io/_uploads/S1drQsxsT.png)
其中我們會用到name這個屬性，它會記錄著該物件的屬性名稱
因此我們在和headerTitleMap(先前設定的標題資料)內的key做比對就可以map到目前的cell要放入的資料了

```
if(key.equals(field.getName())) {
    // 屬性名和titile欄位符合時
    if(key.equals(field.getName())) {
        field.setAccessible(true);// 要設定要能夠讀取物件內private的屬性
        // null處理
        String value = String.valueOf(field.get(dataObj)==null?"":field.get(dataObj));
        cell.setCellValue(String.valueOf(value));							
        break;//塞值後跳出換下一個欄位
    }

}
```
整段處理資料的程式碼
```
for(int i=0; queryList.size() >= i ;i++) {// 行
    Row row = sheet.createRow(i);
    if(i == 0) {//excel title setValue
        int j = 0;
        for(Entry<String, String> title: headerTitleMap.entrySet()) {// 欄
            Cell cell = row.createCell(j);
            cell.setCellValue(title.getValue());											
            j++;
        }
    }

    if(i > 0) {// data row
        IBPMF070100PageResVo dataObj = queryList.get(i-1);
        //null處理
        String renewtimeStr = dataObj.getRenewTime()==null? "": sdf.format(dataObj.getRenewTime());					
        dataObj.setRenewTimeStr(renewtimeStr);

        Field[] fields = dataObj.getClass().getDeclaredFields();
        int j =0;
        for(Entry<String, String> title: headerTitleMap.entrySet()) { // 欄
            String key = title.getKey();
            Cell cell = row.createCell(j);

            for(Field field: fields) {
                // 屬性名和titile欄位符合時
                if(key.equals(field.getName())) {
                    field.setAccessible(true);
                    // null處理
                    String value = String.valueOf(field.get(dataObj)==null?"":field.get(dataObj));
                    cell.setCellValue(String.valueOf(value));							
                    break;//塞值後跳出換下一個欄位
                }
            }
						
            j++;
        }
    }				
}
```





