Java/라이브러리 활용

[Java] Spring Boot 환경에서 Excel 활용하기 -1 : Apache POI 기반 업로드, 읽어오기, 다운로드

adjh54 2025. 2. 12. 20:01
728x170
해당 글에서는 Apache POI 라이브러리를 기반으로 Spring Boot 환경에서 Excel을 활용하는 방법으로 업로드, 읽어오기, 다운로드 기능에 대해 알아봅니다.


 

 

 

💡 [참고] Spring Boot Apache POI 에 대해 상세히 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.
주제 링크
Spring Boot 환경에서 Excel 활용하기 -1 : Apache POI 기반 업로드, 읽어오기, 다운로드 https://adjh54.tistory.com/664
Spring Boot 환경에서 Excel 활용하기 -2 : Apache POI 기반 엑셀 스타일, 필터, 유효성 적용 예시 https://adjh54.tistory.com/668
Apache POI 활용 방법을 담은 예제 Repository https://github.com/adjh54ir/blog-codes/tree/main/spring-boot-excel-poi

 

 

 

1) Apache POI(Poor Obfuscation Implementation)


💡 Apache POI(Poor Obfuscation Implementation)

- Java 프로그래밍 언어를 사용하여 Microsoft Office 파일 형식을 읽고 쓸 수 있게 해주는 오픈소스 라이브러리입니다. 주로 Excel, Word, PowerPoint 등의 파일을 처리하는 데 사용됩니다.
기능 설명
다양한 Office 파일 형식 지원 .xls, .xlsx, .doc, .docx 등 Microsoft Office 파일 형식을 폭넓게 지원
플랫폼 독립성 순수 Java로 작성되어 모든 플랫폼에서 동일하게 동작
성능과 안정성 최적화된 성능과 안정적인 파일 처리 기능 제공
커뮤니티 지원 활발한 개발자 커뮤니티와 지속적인 업데이트 제공

 

1. 주요 기능


기능 설명
Excel 파일 업로드 및 데이터 읽기 사용자가 업로드한 Excel 파일의 내용을 읽어 시스템에서 활용할 수 있도록 처리하는 기능
새로운 Excel 파일 생성 및 데이터 쓰기 시스템에서 새로운 Excel 파일을 생성하고 데이터를 작성하여 저장하는 기능
템플릿 기반 Excel 파일 생성 미리 정의된 템플릿을 기반으로 데이터를 채워 Excel 파일을 생성하는 기능
대용량 Excel 파일 처리 SXSSF를 활용하여 대용량 데이터를 효율적으로 처리하는 기능
Excel 파일 다운로드 생성된 Excel 파일을 사용자가 다운로드할 수 있도록 제공하는 기능

 

 

 

2. Excel 확장자 종류


유형 설명
HSSF (Horrible SpreadSheet Format) Excel 97-2003 (.xls) 파일 형식을 처리하기 위한 컴포넌트
XSSF (XML SpreadSheet Format) Excel 2007 이상 버전의 (.xlsx) 파일 형식을 처리하기 위한 컴포넌트
SXSSF (Streaming XML SpreadSheet Format) 대용량 Excel 파일을 메모리 효율적으로 처리하기 위한 스트리밍 방식의 컴포넌트
CSV (Comma-Separated Values) 단순한 텍스트 기반의 데이터 형식으로, 쉼표(,)로 구분된 값들을 저장하는 형식. Apache POI는 OpenCSV 라이브러리와 함께 사용하여 CSV 파일도 처리 가능

 

 

2) Spring Boot 환경에서 Excel 다루기 -1 : 엑셀 업로드, 읽어오기


1. 의존성 추가


💡 의존성 추가

- 해당 기능을 구현하기 위해서 아래와 같은 의존성을 추가하였습니다. 금일 기준 가장 최신버전으로 적용하였습니다.
- 해당 의존성을 위해서 xls, xlsx, csv 파일들에 대해서 읽어오고 활용할 수 있습니다.
- 또한, 간단한 화면을 구현하기 위해 Thymeleaf를 이용하여서 간단한 화면을 구성하였습니다.
의존성 버전 설명
org.apache.poi:poi 5.4.0 Excel 97-2003(.xls) 형식을 처리하기 위한 기본 POI 라이브러리
org.apache.poi:poi-ooxml 5.4.0 Excel 2007+ (.xlsx) 형식을 처리하기 위한 확장 라이브러리
com.opencsv 5.10 CSV 파일 읽기/쓰기를 위한 라이브러리
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'         // Thymeleaf Template
	implementation 'org.apac[he.poi:poi:5.4.0'
	implementation 'org.apache.poi:poi-ooxml:5.4.0'
	implementation 'com.opencsv:opencsv:5.10'
}

 

 

💡 [참고] Thymeleaf에 대해 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.
 

[Java/Library] Thymeleaf, Thymeleaf Layout 적용하기

해당 글에서는 Spring Boot에서 Thymeleaf에 대한 환경 구성을 하고 Thymeleaf Layout을 적용하기 위한 방법에 대해서 공유합니다.  1) 개발환경💡 Thymeleaf Template / Thymeleaf Template Layout을 구성을 위한 개발

adjh54.tistory.com

 

2. 엑셀 업로드 화면 구현


💡 엑셀 업로드 화면 구현

- 아래와 같이 resources/templates/pages 디렉터리 내에 excelUpload.html이라는 파일을 만들었습니다.
- 해당 코드는 multipart/form-data의 전송 형태로 [POST] excel/upload 엔드포인트로 서버에 엑셀을 전달하도록 구성하였습니다.
- 사용자는 파일을 선택하고 업로드 버튼을 누르면 서버로 전송되는 구조입니다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Excel Upload</title>
</head>
<body>
<h2>Excel File Upload</h2>
<form method="POST" th:action="@{/excel/upload}" enctype="multipart/form-data">
    <input type="file" name="file" accept=".xls,.xlsx" required/>
    <button type="submit">Upload</button>
</form>

<div th:if="${message}" th:text="${message}"></div>
</body>
</html>

 

 

💡 출력 화면

 

[더 알아보기]

💡 multipart/form-data


- HTML 폼에서 파일 업로드를 위해 사용되는 인코딩 타입입니다. 일반적인 폼 데이터와 함께 파일을 서버로 전송할 때 사용됩니다.

 

 

3. ExcelController


💡 ExcelController

1. [GET] /excel/upload 엔드포인트


- Thymeleaf로 구성한 “/pages/excelUpload" 경로의 엑셀 업로드 화면이 출력이 됩니다.

2. [POST] /excel/upload 엔드포인트

- 엑셀 처리를 위해 비즈니스 로직을 처리하는 Service를 호출합니다. 해당 클래스로 MultipartFile file과 페이지를 관리하는 Model을 전달하였습니다.
package com.blog.springbootexcelpoi.controller;

import com.blog.springbootexcelpoi.ExcelService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;

/**
 * Please explain the class!!
 *
 * @author : leejonghoon
 * @fileName : ExcelController
 * @since : 2025. 2. 11.
 */
@Slf4j
@Controller
@RequiredArgsConstructor
@RequestMapping("/excel")
public class ExcelController {

    private final ExcelService excelService;

    /**
     * 엑셀 업로드 화면 출력
     *
     * @return
     */
    @GetMapping("/upload")
    public String uploadForm() {
        return "/pages/excelUpload";
    }

    /**
     * 엑셀 업로드 기능 구현
     *
     * @param file
     * @param model
     * @return
     */
    @PostMapping("/upload")
    public String handleFileUpload(@RequestParam("file") MultipartFile file, Model model) {
        String result = excelService.excelUpload(file, model);
        return result;
    }

}

 

 

4. ExcelService


 💡 ExcelService

- excelUpload() 메서드에서 이를 처리합니다. 이를 통해, 최초 파일정보를 출력하고, Excel 파일을 읽어와서 Header를 출력하고, Body에 해당하는 데이터들은 리스트 객체로 구성하여 출력을 합니다.
- 최종적으로 다시 /excel/upload 엔드포인트로 반환을 합니다.
package com.blog.springbootexcelpoi;

import com.blog.springbootexcelpoi.dto.UserDto;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * 엑셀 처리를 위한 서비스
 *
 * @author : leejonghoon
 * @fileName : ExcelService
 * @since : 2025. 2. 11.
 */
@Slf4j
@Service
public class ExcelService {

    /**
     * 엑셀 업로드를 수행
     *
     * @param file
     * @param model
     * @return
     */
    public String excelUpload(MultipartFile file, Model model) {
        // 파일 정보 출력
        log.debug("파일명: {}", file.getOriginalFilename());
        log.debug("파일 크기: {} bytes", file.getSize());

        try {
            if (file.isEmpty()) {
                model.addAttribute("message", "파일을 선택해주세요.");
                return "/pages/excelUpload";
            }

            // 파일 확장자 검증
            String filename = file.getOriginalFilename();
            if (!filename.endsWith(".xlsx") && !filename.endsWith(".xls")) {
                model.addAttribute("message", "Excel 파일만 업로드 가능합니다.");
                return "/pages/excelUpload";
            }

            List<UserDto> userDtoList = new ArrayList<>();

            // Excel 파일 처리
            Workbook workbook = WorkbookFactory.create(file.getInputStream());
            Sheet sheet = workbook.getSheetAt(0);

            // 헤더 행 읽기
            Row headerRow = sheet.getRow(0);
            for (Cell cell : headerRow) {
                log.debug("{}\\t", cell.getStringCellValue());
            }
            // 데이터 행 읽기
            for (int i = 1; i <= 10; i++) {
                Row row = sheet.getRow(i);

                // 행 들을 리스트 객체로 구성
                userDtoList.add(UserDto.builder()
                        .number((int) row.getCell(0).getNumericCellValue()) // 순번
                        .name(row.getCell(1).getStringCellValue())          // 이름
                        .age((int) row.getCell(2).getNumericCellValue())    // 나이
                        .gender(row.getCell(3).getStringCellValue())        // 성별
                        .contact(row.getCell(4).getStringCellValue())       // 연락처
                        .build());
            }
            log.debug("구성한 리스트 객체 :: {}", userDtoList.toString());
            log.debug("=================================================");

            workbook.close();
            model.addAttribute("message", "파일이 성공적으로 업로드되었습니다.");
        } catch (IOException e) {
            model.addAttribute("message", "파일 처리 중 오류가 발생했습니다: " + e.getMessage());
        }
        return "/pages/excelUpload";
    }
}

 

 

 

5. 엑셀 파일 확인 및 결과 확인


5.1. 업로드 엑셀 파일


 

 

 

5.2. 결과 확인


💡 결과 확인

- 아래와 같이 파일에 대한 정보, 엑셀 Header, 객체화한 데이터들이 출력이 됨을 확인하였습니다.

 

 

 

3) Spring Boot 환경에서 Excel 다루기 -2 : 엑셀 생성 및 다운로드


 

1. 엑셀 다운로드 화면 구현


💡 엑셀 다운로드 화면 구현

- 이전 엑셀 업로드 화면에서 추가적으로 다운로드 화면을 추가하였습니다.
- 해당 부분에서는 [POST] /excel/download 엔드포인트를 호출하는 download 버튼을 구성하였습니다.

 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Excel Upload</title>
</head>
<body>
<h2>Excel File Upload</h2>
<form method="POST" th:action="@{/excel/upload}" enctype="multipart/form-data">
    <input type="file" name="file" accept=".xls,.xlsx" required/>
    <button type="submit">Upload</button>
</form>

<div th:if="${message}" th:text="${message}"></div>

<!--다운로드 화면 추가-->
<h2>Excel File Download</h2>
<form method="POST" th:action="@{/excel/download}">
    <button type="submit">Download</button>
</form>

<div th:if="${downloadMessage}" th:text="${downloadMessage}"></div>

</body>
</html>

 

 

💡 출력 화면

 

 

2. ExcelController


💡 ExcelController

- 위에서 구성한 엑셀 업로드 기능에 엑셀 다운로드 메서드를 구성하였습니다.

- 해당 부분은 비즈니스 로직을 처리하는 ExcelService 내에 excelDownload()를 호출하여서 최종적으로 Resource라는 값을 출력받습니다. 이를 클라이언트에게 전달하며, header와 ContentType을 지정합니다.

1. Header : Content-Disposition: attachment; filename=userList.xlsx
- 브라우저에게 이 응답을 다운로드할 파일로 처리하도록 지시합니다.
- attachment: 파일을 다운로드하도록 지정합니다.
- filename: 다운로드될 파일의 이름을 'userList.xlsx'로 지정합니다.


2. Content Type : Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
- 응답 본문의 미디어 타입을 지정합니다.
- .xlsx 파일의 공식 MIME 타입으로 지정합니다.
- 브라우저가 Excel 파일임을 인식하고 적절히 처리할 수 있도록 합니다.
package com.blog.springbootexcelpoi.controller;

import com.blog.springbootexcelpoi.ExcelService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;

/**
 * Please explain the class!!
 *
 * @author : leejonghoon
 * @fileName : ExcelController
 * @since : 2025. 2. 11.
 */
@Slf4j
@Controller
@RequiredArgsConstructor
@RequestMapping("/excel")
public class ExcelController {

    private final ExcelService excelService;

    /**
     * 엑셀 업로드 화면 출력
     *
     * @return
     */
    @GetMapping("/upload")
    public String uploadForm() {
        return "/pages/excelUpload";
    }

    /**
     * 엑셀 업로드 기능 구현
     *
     * @param file
     * @param model
     * @return
     */
    @PostMapping("/upload")
    public String handleFileUpload(@RequestParam("file") MultipartFile file, Model model) {
        String result = excelService.excelUpload(file, model);
        return result;
    }

    /**
     * 엑셀 다운로드 기능 구현
     * @return
     * @throws IOException
     */
    @PostMapping("/download")
    public ResponseEntity<Resource> downloadExcel(Model model) {

        Resource result = excelService.excelDownload(model);

        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=userList.xlsx")
                .contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
                .body(result);
    }

}

 

 

3. ExcelService


💡 ExcelService

- 엑셀 다운로드의 비즈니스를 처리하는 excelDownload() 함수입니다.


1. 시트 생성 : createSheet()으로 시트를 생성합니다.
2. 헤더 생성 : sheet 내에 createRow() 메서드를 통해서 헤더를 생성합니다.
3. 데이터 생성 : 헤더 아래에 createRow() 메서드를 통해 데이터를 생성합니다.
4. 열 너비 자동 설정 : autoSizeColumn() 메서드를 통해 열 너비를 자동으로 생성합니다.
5. 파일 생성 : 생성된 Excel workbook의 내용을 ByteArrayOutputStream에 작성합니다. 이를 통해 바이트 형태로 변환이 됩니다.
6. 시트 종료 : close()으로 시트를 종료합니다.
7. 리소스 구성 : 구성한 바이트 데이터를 HTTP 응답에 전달할 수 있는 ByteArrayResource로 구성하여 반환합니다.
package com.blog.springbootexcelpoi;

import com.blog.springbootexcelpoi.dto.UserDto;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * 엑셀 처리를 위한 서비스
 *
 * @author : leejonghoon
 * @fileName : ExcelService
 * @since : 2025. 2. 11.
 */
@Slf4j
@Service
public class ExcelService {

    /**
     * 엑셀 다운로드
     *
     * @param file
     * @param model
     * @return
     */
    public Resource excelDownload(Model model) {

        Workbook workbook = new XSSFWorkbook();
        Sheet sheet = workbook.createSheet("사용자 정보");

        // 헤더 행 생성
        Row headerRow = sheet.createRow(0);
        String[] headers = {"순번", "이름", "나이", "성별", "연락처"};
        for (int i = 0; i < headers.length; i++) {
            Cell cell = headerRow.createCell(i);
            cell.setCellValue(headers[i]);
        }

        // 데이터 행 생성
        Object[][] data = {
                {1, "한국인", 35, "남", "010-0000-0000"},
                {2, "박원희", 11, "여", "010-1234-0000"},
                {3, "이국한", 23, "여", "010-5678-0000"},
                {4, "김명희", 27, "여", "010-9010-0000"},
                {5, "김철민", 29, "남", "010-8888-0000"},
        };

        // Sheet 내에 헤더 / 데이터 행 구성
        for (int i = 0; i < data.length; i++) {
            Row row = sheet.createRow(i + 1);
            for (int j = 0; j < data[i].length; j++) {
                Cell cell = row.createCell(j);

                // 문자 처리
                if (data[i][j] instanceof String) {
                    cell.setCellValue((String) data[i][j]);
                }
                // 숫자 처리
                if (data[i][j] instanceof Integer) {
                    cell.setCellValue((Integer) data[i][j]);
                }
            }
        }

        // 열 너비 자동 조정
        for (int i = 0; i < headers.length; i++) {
            sheet.autoSizeColumn(i);
        }

        // 파일 생성
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try {
            workbook.write(outputStream);
            workbook.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        ByteArrayResource resource = new ByteArrayResource(outputStream.toByteArray());
        model.addAttribute("downloadMessage", "다운로드가 완료되었습니다.");
        return resource;

    }

}

 

 

4. 결과 확인


💡 결과 확인

- 아래와 같이 다운로드가 되어서 데이터 출력을 확인할 수 있습니다.

 

 

 

 

💡 [참고] 해당 소스코드는 아래의 Github Repository 내에서 확인이 가능합니다.
 

blog-codes/spring-boot-excel-poi at main · adjh54ir/blog-codes

Contributor9 티스토리 블로그 내에서 활용한 내용들을 담은 레포지토리입니다. Contribute to adjh54ir/blog-codes development by creating an account on GitHub.

github.com

 

 

 
오늘도 감사합니다😀

 

 

 

 

그리드형