허원철의 개발 블로그

Spring Boot - Excel Upload & Download 본문

web

Spring Boot - Excel Upload & Download

허원철 2017. 5. 5. 12:20
이번 글은 Spring Boot를 이용하여 Excel을 Upload 와 Download을 다루는 예제 글입니다.


자바진영에서 엑셀을 Upload, Download하기 위해 POI를 가장 많이 사용합니다. 엑셀 버전마다 다를 수 있는데, xls의 경우는 poi, xlsx의 경우는 poi-ooxml를 추가하면 됩니다. (xls의 경우, Excel(5.0/95)이하는 불가능한 걸로 알고 있습니다.)
 
 
1. Gradle
  
1
2
3
4
5
6
7
8
9
dependencies {
    compile('org.apache.poi:poi-ooxml:3.16'// .xlsx
    compile('org.apache.poi:poi:3.16'// .xls
 
    compile('org.projectlombok:lombok:1.16.6')
 
    compile('org.springframework.boot:spring-boot-starter-web')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}
cs
  
  
2. Upload
  
- Java 8의 Function, Stream을 이용하여 Component를 추가해보겠습니다.
  
2-1. Component
 
1) MultipartFile과 Function을 이용하여 Component를 만들어줍니다.
    
1
2
3
4
5
6
7
8
@Component
public class ExcelReadComponent {
 
    public <T> List<T> readExcelToList(final MultipartFile multipartFile,
                                       final Function<Row, T> rowFunc) throws IOException, InvalidFormatException {
    // ...
    }
}
cs
 
2) File을 Workbook으로 변환하기 전에 엑셀 파일인지 검증해줍니다.
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public <T> List<T> readExcelToList(final MultipartFile multipartFile,
                                   final Function<Row, T> rowFunc) throws IOException, InvalidFormatException {
 
    final Workbook workbook = readWorkbook(multipartFile);
        
    // ...
}
 
private Workbook readWorkbook(MultipartFile multipartFile) throws IOException, InvalidFormatException {
    verifyFileExtension(multipartFile);
    return multipartFileToWorkbook(multipartFile);
}
 
private void verifyFileExtension(MultipartFile multipartFile) throws InvalidFormatException {
    if!isExcelExtension(multipartFile.getOriginalFilename()) ) {
        throw new InvalidFormatException("This file extension is not verify");
    }
}
 
private boolean isExcelExtension(String fileName) {
    return fileName.endsWith(ExcelConfig.XLS) || fileName.endsWith(ExcelConfig.XLSX);
}
 
private boolean isExcelXls(String fileName) {
    return fileName.endsWith(ExcelConfig.XLS);
}
 
private boolean isExcelXlsx(String fileName) {
    return fileName.endsWith(ExcelConfig.XLSX);
}
cs
 
3) Workbook으로 변환 뒤, Stream을 이용하여 Row 갯수 만큼 Domain로 바꾸고 해당 Stream을 List로 변환해줍니다.
  
1
2
3
4
5
6
7
8
9
10
11
12
public <T> List<T> readExcelToList(final MultipartFile multipartFile,
                                   final Function<Row, T> rowFunc) throws IOException, InvalidFormatException {
 
    final Workbook workbook = readWorkbook(multipartFile);
    final Sheet sheet = workbook.getSheetAt(0);
    final int rowCount = sheet.getPhysicalNumberOfRows();
 
    return IntStream
            .range(0, rowCount)
            .mapToObj(rowIndex -> rowFunc.apply(sheet.getRow(rowIndex)))
            .collect(Collectors.toList());
}
cs
 
 
2-2. Controller & Domain
 
1) 위에서 작성한 Component를 이용하여 JSON 형태로 반환해줍니다.
  
1
2
3
4
5
6
7
8
9
10
11
12
@RestController
@RequestMapping("upload")
public class UploadExcelController {
 
    @Autowired ExcelReadComponent excelReadComponent;
 
    @PostMapping("excel")
    public List<Product> readExcel(@RequestParam("file") MultipartFile multipartFile)
                                                throws IOException, InvalidFormatException {
        return excelReadComponent.readExcelToList(multipartFile, Product::new);
    }
}
cs
   
2) Row를 이용하여 Domain 객체를 만들어줍니다. (간단한 예제이기 때문에 유효성 검사가 없으니 상세한 작업을 원한다면 추가해줘야 할 것 입니다.)
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Data
@AllArgsConstructor
public class Product implements Serializable {
 
    private String uniqueId;
 
    private String name;
 
    private String comment;
 
    public Product(Row row) {
        this(row.getCell(0).getStringCellValue(),
            row.getCell(1).getStringCellValue(),
            row.getCell(2).getStringCellValue());
    }
}
cs
  
 
3. Download
 
- spring 4.x 이전에는 AbstractExcelView를 사용하였으나, 이후에는 AbstractXlsView, AbstractXlsxView, AbstractXlsxStreamingView를 이용해야 합니다.
- 각각의 Component로 등록을 해준 뒤, ModelAndView에서 해당 name으로 반환할 때, 인식할 수 있도록 @EnableWebMvc를 추가해줍니다.
- 각각의 AbstractXXXView는 내부적으로 기본 셋팅(Workbook 등등...) 되어 있기 때문에, 공통적으로 buildExcelDocument()를 오버라이딩하면 됩니다.
- 그래서 공통적으로 사용할 ExcelCommonUtil을 만들었습니다. 
 
1
2
3
4
5
6
7
@Override
protected void buildExcelDocument(Map<String, Object> model,
                                  Workbook workbook,
                                  HttpServletRequest request,
                                  HttpServletResponse response) throws Exception {
    new ExcelCommonUtil(workbook, model, response).createExcel();
}
cs
 
- workbook은 해당 Excel형식에 맞는 인스턴스 객체가 됩니다. (XSSFWorkbook, SXSSFWorkbook, HSSFWorkbook 등)
- model은 Controller에서 ModelAndView에서 담은 Map을 뜻합니다.
- response는 클라이언트에게 반환해줄 객체입니다.
 
3-1. ExcelCommonUtil
  
1
2
3
4
5
6
7
8
9
10
public void createExcel() {
    // 1)
    setFileName(response, mapToFileName());
    // 2)
    Sheet sheet = workbook.createSheet();
    // 3)
    createHead(sheet, mapToHeadList());
    // 4)
    createBody(sheet, mapToBodyList());
}
cs
 
1) Download될 Excel 파일에 대해 명명을 해줍니다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void setFileName(HttpServletResponse response, String fileName) {
    response.setHeader("Content-Disposition",
                       "attachment; filename=\"" + setFileExtension(fileName) + "\"");
}
 
private String setFileExtension(String fileName) {
    if ( workbook instanceof XSSFWorkbook) {
        fileName += ".xlsx";
    }
    if ( workbook instanceof SXSSFWorkbook) {
        fileName += ".xlsx";
    }
    if ( workbook instanceof HSSFWorkbook) {
        fileName += ".xls";
    }
 
    return fileName;
}
cs
  
2) 해당 Excel 파일에 하나의 시트를 만들어줍니다. (단순 예제이기 때문에 시트 하나만 만들어줬습니다.)

3) head에 해당하는 데이터를 넣어줍니다.
 
1
2
3
private void createHead(Sheet sheet, List<String> headList) {
    createRow(sheet, headList, 0);
}
cs
 
4) body에 해당하는 데이터를 넣어줍니다.

1
2
3
4
5
6
private void createBody(Sheet sheet, List<List<String>> bodyList) {
    int rowSize = bodyList.size();
    for (int i = 0; i < rowSize; i++) {
        createRow(sheet, bodyList.get(i), i + 1);
    }
}
cs
 
※ createHead, createBody에서 공통으로 쓰인 메소드
  
1
2
3
4
5
6
7
8
9
private void createRow(Sheet sheet, List<String> cellList, int rowNum) {
    int size = cellList.size();
    Row row = sheet.createRow(rowNum);
 
    for (int i = 0; i < size; i++) {
        row.createCell(i)
            .setCellValue(cellList.get(i));
    }
}
cs

3-2. Controller
 
- ModelAndView를 이용하여 각각의 Component를 호출해줍니다.
  
1
2
3
4
5
6
7
8
9
10
11
@Controller
@RequestMapping("download")
public class DownloadExcelController {
 
    @GetMapping("excel-xls")
    public ModelAndView xlsView() {
        return new ModelAndView("excelXlsView", getDefaultMap());
    }
 
    // ...
}
cs
 
 
참고
 
- 조금 더 자세한 코드 내용은 깃헙을 참고하시기 바랍니다.
 


'web' 카테고리의 다른 글

Spring Boot - CORS  (410) 2017.06.25
Spring Boot - Async 제대로 사용하기  (407) 2017.05.18
Spring Boot - Data JPA & ModelMapper  (414) 2017.04.30
Spring Boot - MyBatis  (396) 2017.04.13
Spring Boot + JPA + Time package Issue  (437) 2017.03.22
Comments