iT邦幫忙

2024 iThome 鐵人賽

DAY 24
0

能夠讀取Excel是JasperReports沒有的功能,今天就使用Apache POI來讀取Apache POI-寫入(匯出)Excel這篇所匯出的學生成績資料表。

情境:讀取學生成績資料表

  • Apache POI讀取Excel的時候有個好用的類別 「WorkbookFactory」 ,可以在API文件中看到使用create()方法,能夠傳入File、Inputstream等等參數,就能輕鬆取得文件的Workbook。 不論要讀取的Excel是.xls還是.xlsx,不用寫判斷邏輯就可以拿到Workbook,充分顯示物件導向中多型的優點。

讀取Excel

下圖是學生成績資料表的資料:

我想將讀取到的欄位資料放到StudentCourseScoreReportModel這個物件中

@Data
@AllArgsConstructor
@NoArgsConstructor
public class StudentCourseScoreReportModel {
    private String studentNumber;

    private String fullName;

    private String grade;

    private String courseDesc;

    private String departmentDesc;

    private Integer score;

    private String testDate;
}

步驟如下,第幾步可以對應註解來看:

  1. 一樣要先準備好欄位名稱的資料。
  2. 這個範例是用檔案路徑取得檔案InputStream,再以WorkbookFactory取得Workbook。
  3. 由於我的Excel只有一個sheet,可以用getSheetAt()取第一個,如果要寫得更通用一些可以用getNumberOfSheets()取得sheet的數量再以for loop遍歷。
  4. 一張sheet有很多列,如果全部遍歷一次會顯得沒效率,因此可以用getLastRowNum()取得有內容的最後一列的index,只處理有資料的部分就好。
  5. getRow取得Row,並且準備好StudentCourseScoreReportModel,在下面的步驟要放入Row中讀取到的資料。
  6. 行的範圍取到欄位的數量,以getCell()取得Cell。
  7. 由於不同欄位的型別可能不同,必須依照欄位名稱判斷資料類型,再將資料放入model物件中。
    • 這個範例只有用到String和Integer,CellType還有很多種資料類型,甚至連公式也能讀取
public void readExcel(String filePath) {
    // 1. 欄位名稱
    String[] columnNames = {"學號", "科系", "年級", "姓名", "課程", "成績", "考試日期"};

    List<StudentCourseScoreReportModel> modelList = new ArrayList<>();

    try {
        InputStream inputStream = Files.newInputStream(Paths.get(filePath));
        // 2. 取得Workbook
        Workbook workbook = WorkbookFactory.create(inputStream);
        // 3. 取得sheet
        Sheet sheet = workbook.getSheetAt(0);
        if (sheet != null) {
            // 4. 有內容的最後一列index
            int lastRowNumber = sheet.getLastRowNum();
            // 第1列為標題,從第2列開始讀取
            for (int i = 2; i <= lastRowNumber; i++) {
                // 5. 取得row
                Row row = sheet.getRow(i);
                StudentCourseScoreReportModel model = new StudentCourseScoreReportModel();
                
                for (int j = 0; j < columnNames.length; j++) {
                    if (row != null) {
                        // 6. 取得cell
                        Cell cell = row.getCell(j);
                        if (cell == null) {
                            continue;
                        }
                        // 7. 按照欄位名稱取值,放入model
                        switch (columnNames[j]) {
                            case "學號":
                                if (cell.getCellType() == CellType.STRING) {
                                    String studentNumber = cell.getStringCellValue();
                                    if (studentNumber != null) {
                                        model.setStudentNumber(studentNumber);
                                    }
                                }
                                break;

                            case "科系":
                                if (cell.getCellType() == CellType.STRING) {
                                    String departmentDesc = cell.getStringCellValue();
                                    if (departmentDesc != null) {
                                        model.setDepartmentDesc(departmentDesc);
                                    }
                                }
                                break;

                            case "年級":
                                if (cell.getCellType() == CellType.STRING) {
                                    String grade = cell.getStringCellValue();
                                    if (grade != null) {
                                        model.setGrade(grade);
                                    }
                                }
                                break;

                            case "姓名":
                                if (cell.getCellType() == CellType.STRING) {
                                    String fullName = cell.getStringCellValue();
                                    if (fullName != null) {
                                        model.setFullName(fullName);
                                    }
                                }
                                break;

                            case "課程":
                                if (cell.getCellType() == CellType.STRING) {
                                    String courseDesc = cell.getStringCellValue();
                                    if (courseDesc != null) {
                                        model.setCourseDesc(courseDesc);
                                    }
                                }
                                break;

                            case "成績":
                                if (cell.getCellType() == CellType.NUMERIC) {
                                    Double score = cell.getNumericCellValue();
                                    if (score != null) {
                                        model.setScore(score.intValue());
                                    }
                                }
                                break;

                            case "考試日期":
                                if (cell.getCellType() == CellType.STRING) {
                                    String testDate = cell.getStringCellValue();
                                    if (testDate != null) {
                                        model.setTestDate(testDate);
                                    }
                                }
                                break;

                            default:
                                break;
                        }
                    }
                }
                modelList.add(model);
            }
        }
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

執行到最後就能拿到List<StudentCourseScoreReportModel>資料了

▲補充
Workbook介面有繼承Iterable<Sheet>;Sheet介面有繼承Iterable<Row>;Row介面有繼承Iterable<Cell>。表示除了範例使用的遍歷方式外,可以直接以foreach遍歷資料。

Workbook workbook = WorkbookFactory.create(inputStream);
for (Sheet sheet : workbook) {
    for (Row row : sheet) {
        for (Cell cell : row) {
        }
    }
}

Apache POI讀取Excel比較繁瑣的部分就是判斷Cell中資料的型別或格式,如果要寫得更謹慎就要多一些例外處理,才能判斷日期或數字格式是否正確,不然cell.getNumericCellValue()cell.getDateCellValue()都滿容易有Exception的狀況發生。


Reference


上一篇
Apache POI-SXSSFWorkbook到底有多快
下一篇
Apache POI-Cell的樣式、格式設定
系列文
Java工程師的報表入門與實作30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言