iT邦幫忙

2024 iThome 鐵人賽

DAY 12
0
Software Development

Java工程師的報表入門與實作系列 第 12

JasperReports-匯出多個sheet的Excel

  • 分享至 

  • xImage
  •  

資料單純時用一個模板匯出excel就能滿足,但對於大型資料集或需要區分不同類別的資料時,想將資料放在不同sheet呈現的話要怎麼做呢?以下將介紹如何實現匯出多個工作表Excel。

情境:匯出學生資料與考試成績表Excel

以一個sheet一個模板為例

多個sheet會有兩種情況:

  1. 每個sheet都是同樣的呈現方式,但顯示的資料類型不同,例如都是學生資料,一個sheet顯示一個班級的學生資料
  2. 一個sheet就是一種資料與呈現方式,因此一個sheet的資料會需要一個符合的報表模板。這次以這種情況為例,我想將前面一直在用的「學生資料表」與「學生考試成績表」各作為一個sheet,匯出在同一個excel檔中,模板已經準備好了。其實就是不想做新的模板

查詢資料

第一步先查詢要放入這兩個報表模板的資料,各回傳一個List。

// 1. 查詢學生基本資料
List<StudentDataReportModel> studentDataReportModelList = 
reportDemoService.getStudentAndDepartmentData();

// 2.查詢學生考試成績資料
List<StudentCourseScoreReportModel> studentCourseScoreReportModelList = 
reportDemoService.getStudentCourseScoreData();

設定報表參數

每個模板都能有它自己的參數,雖然這兩個模板的參數都一樣是日期P${date},為了不混淆我還是分為兩個parametersMap。

// 學生基本資料的參數
Map<String, Object> studentDataParameters = new HashMap<>();
// 學生考試成績資料的參數
Map<String, Object> studentCourseScoreParameters = new HashMap<>();
LocalDate localDate = new Date().toInstant()
                        .atZone(ZoneId.systemDefault()).toLocalDate();
studentDataParameters.put("date", localDate
               .format(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
studentCourseScoreParameters.put("date", localDate
               .format(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));

封裝報表資料

因為一個sheet的資料等於是一張報表,最後會合在同一個Excel匯出,所以將需要的報表資料封裝在物件SheetReportDetail中,能簡化後續的動作也不容易漏掉資料。

  1. 先建立SheetReportDetail物件,屬性為必須放入兩個模板分別的報表資料、模板路徑、報表參數,sheet名稱如果不想用預設的,也是可以自訂義。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SheetReportDetail {
    // 報表資料
    private List dataSourceList;

    // 模板路徑
    private String reportPath;

    // 報表parametersMap
    private Map<String, Object> parametersMap;

    // excel的sheet名稱
    private String sheetName;
}
  1. 連同上一步的報表參數,將資料放入物件,多個物件組成ArrayList。
// 模板路徑
String reportPath1 = "/Report/Jasper/StudentDataReportExcel.jrxml";
String reportPath2 = "/Report/Jasper/StudentCourseScoreReport.jrxml";
// sheet名稱
String sheetName1 = "學生資料表";
String sheetName2 = "學生考試成績表";

// 資料放入SheetReportDetail
SheetReportDetail sheetReportDetail1 = new SheetReportDetail(studentDataReportModelList, reportPath1, studentDataParameters, sheetName1);
SheetReportDetail sheetReportDetail2 = new SheetReportDetail(studentCourseScoreReportModelList, reportPath2, parametersMap, sheetName2);

// 組成ArrayList
List<SheetReportDetail> sheetReportDetailList = new ArrayList<>();
sheetReportDetailList.add(sheetReportDetail1);
sheetReportDetailList.add(sheetReportDetail2);

匯出Excel

我將匯出的步驟封裝在ExportReportUtil的templateToByteMultipleSheet()方法。

  • 因為上個步驟的封裝,多個sheet的資料可以用for loop跑完報表生命週期
  • sheet名稱會由SimpleXlsxReportConfiguration設定,型別是String[],String[]初始化時指定長度為List的長度,依執行順序放入我們設定的sheet名稱
  • SimpleXlsxReportConfiguration是JasperReports中用來設定Excel報表匯出的參數,可以透過這個類別來微調Excel檔案的格式和行為
    • 在這個例子中,可以用setSheetNames()方法設定sheet名稱
    • 還有一個很實用的方法setDetectCellType(),設定為true的話,excel就會偵測這個值的型別並轉換為對應的格式,匯出Excel時會比較是否偵測cellType的差異
    • 如果匯出的是pdf檔,也有SimplePdfReportConfiguration,只是如果是使用JasperExportManager簡易匯出的話,沒有辦法使用此類別設定參數,只有JRPdfExporter才能以setConfiguration()方法將該類別所設定的參數應用
  • 整個報表生命週期與匯出一個sheet的相同,只是多個sheet最後所產生的JasperPrint會組成List,最後以exporter.setExporterInput(SimpleExporterInput.getInstance(printList))合併起來並由JRXlsxExporter匯出
public static byte[] templateToByteMultipleSheet(List<SheetReportDetail> sheetReportDetailList) throws Exception {

    try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
        List<JasperPrint> printList = new ArrayList<>();
        String[] sheetNames = new String[sheetReportDetailList.size()];

        for (int i = 0; i < sheetReportDetailList.size(); i++) {
            SheetReportDetail detail = sheetReportDetailList.get(i);
            // 放入sheet名稱
            sheetNames[i] = detail.getSheetName();
            String path = detail.getReportPath();
            List dataSourceList = detail.getDataSourceList();
            Map<String, Object> parametersMap = detail.getParametersMap();

            // 以JasperCompileManager將jrxml模板編譯成jasper文件
            JasperReport jasperReport = JasperCompileManager
                .compileReport(ExportReportUtil.class.getResourceAsStream(path));

            // 將Java集合資料來源與Jasper報表進行綁定
            JRDataSource dataSource = 
                new JRBeanCollectionDataSource(dataSourceList, true);

            // 將資料填入報表
            JasperPrint print = JasperFillManager
                .fillReport(jasperReport, parametersMap, dataSource);
            printList.add(print);
        }

        SimpleXlsxReportConfiguration xlsxReportConfiguration = 
            new SimpleXlsxReportConfiguration();
        // setDetectCellType使excel偵測這個值的型別並轉換為對應的格式
        xlsxReportConfiguration.setDetectCellType(true);
        // 設定sheet名稱
        xlsxReportConfiguration.setSheetNames(sheetNames);

        JRXlsxExporter exporter = new JRXlsxExporter();
        exporter.setConfiguration(xlsxReportConfiguration);
        exporter.setExporterInput(SimpleExporterInput.getInstance(printList));
        exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(byteArrayOutputStream));
        exporter.exportReport();

        return byteArrayOutputStream.toByteArray();
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

調整Jaspersoft studio的設定

因為是匯出excel檔,不需要分頁,我將ignore pagination取消勾選,不過因為我們有兩個sheet,兩張模板,所以
要將兩張模板的ignore pagination都取消勾選!
要將兩張模板的ignore pagination都取消勾選!
要將兩張模板的ignore pagination都取消勾選!

不要像我一樣第一次做的時候只取消勾選其中一個模板,匯出excel的時候發現還是有分頁(分頁的地方會空一列,並重複顯示欄位名稱),找了很久才發現原因...

學生資料與考試成績表Excel檔

  • 實際匯出excel檔後,我們可以看到有兩個sheet,並且也是我們所設定的sheet名稱。

  • 如果將前面所提的setDetectCellType(),設定為false或是不設定的話,匯出的一些數值相關的格式就沒有被偵測為數值,需要自行手動轉換,對於某些需求或情境來說是不方便的,因此還是滿推薦使用這個自動偵測型別的功能的


Reference


上一篇
JasperReports-自訂變數
下一篇
JasperReports-報表沒有資料時的處理
系列文
Java工程師的報表入門與實作30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言