본문 바로가기

Backend/SpringBoot

SpringBoot의 POI을 이용해서 엑셀 파일 읽기

  안녕하세요!

이번 포스팅에서는 SpringBoot의 POI를 이용해서

엑셀 데이터를 읽고 뷰에 뿌리는 코드를 작성해보겠습니다. 👩🏻‍💻

 

전체 코드는 Github에서 확인이 가능합니다


사전준비

1. POI

마이크로소프트 오피스 파일 포맷(ex. xlsx, ppt ...)을 자바 언어로서 읽고 쓰는 기능을 제공해주는 라이브러리입니다.

2. 프로젝트 설명

엑셀을 업로드하면 html 뷰로 보여주는 코드를 작성해보도록 하겠습니다.

3. 의존성

(SpringBoot에서 프로젝트를 만들 때 기본적으로 추가할 수 있는 의존성을 의미합니다)

  • Web
  • Thymeleaf
  • Lombok

4. 엑셀 파일 준비

엑셀 파일을 준비해줍니다.

저는 더미 데이터를 이용해 아래와 같은 파일을 만들었습니다.

이름.xlsx
0.01MB

이름.xlsx


구현

1. 의존성 추가

pom.xml

<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi</artifactId>
  <version>3.11</version>
</dependency>
<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi-ooxml</artifactId> <!-- 엑셀 2007 이상 버전에서 사용 -->
  <version>3.11</version>
</dependency>


<dependency>
  <groupId>commons-io</groupId>
  <artifactId>commons-io</artifactId>
  <version>2.4</version>
</dependency>

엑셀 파일을 읽기 위해 필요한 poi, poi-ooxml

확장자를 가져오기 위해 필요한 commons-io를 추가해줍니다.

 

build.gradle

compile group: 'org.apache.poi', name: 'poi', version: '3.11'
compile group: 'org.apache.poi', name: 'poi-ooxml', version: '3.11'

compile group: 'commons-io', name: 'commons-io', version: '2.4'

Gradle인 경우에는 위 의존성을 사용하면 됩니다.

 

2. 파일 입력 폼 작성

excel.html

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="UTF-8">
    <title>엑셀 업로드</title>
  </head>

  <body>
    <form th:action="@{/excel/read}" method="POST" enctype="multipart/form-data">
      <input type="file" th:name="file">
      <input th:type="submit" value="제출" />
    </form>
  </body>

</html>

파일을 업로드할 수 있는 간단한 폼을 만들어줍니다.

 

3. 객체 작성

ExcelData.java

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class ExcelData {

  private Integer num;

  private String name;

  private String email;
  
}

엑셀 데이터들을 넣어놓을 객체를 만들어줍니다.

저는 아까 엑셀을 만들 때 순번, 이름, 이메일 항목이 있었기 때문에 위와 같이 작성했습니다.

 

4. 컨트롤러 작성

ExcelController.java

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.io.FilenameUtils;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

@Controller
public class ExcelController {

  @GetMapping("/excel")
  public String main() { // 1
    return "excel";
  }


  @PostMapping("/excel/read")
  public String readExcel(@RequestParam("file") MultipartFile file, Model model)
      throws IOException { // 2

    List<ExcelData> dataList = new ArrayList<>();

    String extension = FilenameUtils.getExtension(file.getOriginalFilename()); // 3

    if (!extension.equals("xlsx") && !extension.equals("xls")) {
      throw new IOException("엑셀파일만 업로드 해주세요.");
    }

    Workbook workbook = null;

    if (extension.equals("xlsx")) {
      workbook = new XSSFWorkbook(file.getInputStream());
    } else if (extension.equals("xls")) {
      workbook = new HSSFWorkbook(file.getInputStream());
    }

    Sheet worksheet = workbook.getSheetAt(0);

    for (int i = 1; i < worksheet.getPhysicalNumberOfRows(); i++) { // 4

      Row row = worksheet.getRow(i);

      ExcelData data = new ExcelData();

      data.setNum((int) row.getCell(0).getNumericCellValue());
      data.setName(row.getCell(1).getStringCellValue());
      data.setEmail(row.getCell(2).getStringCellValue());

      dataList.add(data);
    }

    model.addAttribute("datas", dataList); // 5

    return "excelList";

  }
}

1) 엑셀 폼을 보여주는 매핑 메소드를 작성합니다.

2) @RequestParam을 이용해서 파일을 전달 받습니다. 자료형은 MultipartFile을 사용합니다.

3) 아까 추가해뒀던 commons-io에 있는 파일 확장자 가져오기 기능을 이용해서

    확장자를 가져오고 엑셀파일(xls, xlsx)이 아닌 경우에는 예외를 던집니다.

더보기

apache tika 라이브러리를 사용하면 파일 스트림의 앞에 매직 넘버를 읽어서 파일 타입을 유추하므로 확장자보다 좀 더 정확하게 읽을 수 있을 것 같습니다. 

라는 댓글을 보고(댓글 달아주신 akaz님 감사합니다!) 3번을 보완해서 소스코드를 짜서 업데이트를 했습니다. (Github)

apache tika를 적용하는 방법을 간단하게 요약하면 아래와 같습니다. 관련된 파일명은 괄호 안에 적어두었으니 소스코드를 비교해가면서 확인하면 이해하는데 도움이 될 것 같습니다!

apache tika 적용 방법

  1. 의존성 추가 (build.gradle)
  2. Tika 객체를 생성한 후에, detect(file)을 사용해서 MIME 타입 얻어내기 (ExcelController.java)
  3. MIME 타입이 원하는 타입인지 확인 (ExcelUtils.java)

 

얻을 수 있는 장점

텍스트 파일을 xlsx로 변경

위 사진과 같이 .txt 파일을 .xlsx로 속인 후에 올리면, 확장자만 가지고 비교하면 잡을 수 없지만, apache tika를 사용하게 되면 MIME 타입을 통해 가짜 파일을 필터링할 수 있다는 장점이 있습니다. 

하지만 apache tika만 가지고 비교를 할 수 없는게, xlsx, ppt 등 Micosoft Office 파일이 모두 같은 MIME( application/x-tika-ooxml)으로 나오기 때문에 확장자를 이용해 사용하는 방식과 함께 쓰면 가장 좋을 것 같습니다.

 

apache tika 공식문서

apache tika에서 지원하는 MIME 포맷

 

4) 행 개수만큼 반복문을 돌며 데이터를 가져옵니다.

    getCell(열 번호)를 통해 가져올 수 있으며, 아래와 같은 메소드를 제공합니다.

    .getNumericCellValue()> : 실수 데이터 가져오기

    .getStringCellValue() : 문자열 데이터 가져오기

    .getBooleanCellValue() : 논리 데이터 가져오기

5) 엑셀 데이터들을 넣어놓은 객체 리스트들을 반환합니다.

 

5. 리스트 작성

excelList.html

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
</head>
<body>
  <table class="table table-striped">
    <thead>
    <tr>
      <th scope="col">#</th>
      <th scope="col">이름</th>
      <th scope="col">이메일</th>
    </tr>
    </thead>
    <tbody>
    <tr th:each="data : ${datas}" >
      <td scope="row" th:text="${data.num}"></td>
      <td th:text="${data.name}"></td>
      <td th:text="${data.email}"></td>
    </tr>
    </tbody>
  </table>
</body>
</html>

Thymeleaf를 통해 데이터들을 순회하며 출력해줍니다.

이렇게 하면 구현이 끝납니다!

확인해보겠습니다.

파일 선택 전
파일 선택 후

폼을 보여주는 링크에 접속해서 파일을 업로드하고 submit 버튼을 누르면

 

엑셀에 있는 데이터들을 뷰에서 확인할 수 있습니다!


정리

POI 라이브러리를 이용하시면 위와 같이 간단하게 엑셀 데이터를 조작할 수 있습니다.

혹시 글을 읽으면서 잘못된 내용이 있으면 댓글로 알려주시면 감사하겠습니다!

읽어주셔서 감사합니다! 😊


참고링크

https://stackoverflow.com/questions/50849800/how-to-read-excel-file-using-spring-boot