Apache POI 라이브러리는 자바 생태계에서 OOXML에 기반을 둔 다양한 Office 관련 파일들을 조작할 수 있도록 도와주는 라이브러리이다
오늘은 Apache POI 를 이용해 엑셀 파일에서 데이터를 불러오고 처리하는 작업을 해보자
POI 라이브러리에서 사용하는 엑셀 파일을 Workbook, 우리나라 말로는 통합 문서 정도로 생각하면 된다
Workbook의 종류에는 구 버전인 HSSFWorkbook(.xls) 와 현재 통용되는 버전인 엑셀 2007이상 버전의 XSSFWorkbook(.xlsx)가 있다
이 글에서는 XSSFWorkbook을 기준으로 작성하겠다
java에서 파일을 읽어온다는 표현을 많이 사용하지만 그 보다는 개인적으로 연다는 표현을 사용하는게 더 적절하지 않을까 싶다
왜냐하면 우리가 GUI로 엑셀 파일을 열고 쓰며 서식을 정해주고 하는 등 대부분의 기능을 구현할 수 있기 때문이다
보통의 경우 MultipartFile을 통해 request를 받기 때문에 이를 이용해 먼저 엑셀 파일을 RequestPart로 받는 컨트롤러를 하나 만들어주자
@RequiredArgsConstructor
@RestController
@RequestMapping(path = "v1")
public class ExcelController {
private final ExcelService excelService;
@PostMapping("/excel")
public Response create(@RequestPart MultipartFile file) throws Exception {
return excelService.create(file);
}
}
MultipartFile을 엑셀 Workbook으로 변환하는 건 간단하다
라이브러리 문서를 보면 알다 시피 XSSFWorkbook 생성자에는 다양한 타입의 인자를 넣어 Workbook 객체를 생성할 수 있다
그 중 InputStream을 넣어 Workbook 객체를 만들어주자
@Slf4j
@RequiredArgsConstructor
@Service
public class ExcelServiceImpl implements ExcelService {
@Transactional
public void create(MultipartFile file) throws IOException {
XSSFWorkbook workbook = new XSSFWorkbook(file.getInputStream());
}
}
이제 위의 workbook객체를 실제 엑셀 파일과 같이 다뤄주면 된다
데이터를 다루기 위해서는 엑셀 파일의 sheet를 먼저 골라야 한다
index나 sheetName으로 선택할 수 있다
@Slf4j
@RequiredArgsConstructor
@Service
public class ExcelServiceImpl implements ExcelService {
@Transactional
public void create(MultipartFile file) throws IOException {
XSSFWorkbook workbook = new XSSFWorkbook(file.getInputStream());
XSSFSheet sheet = workbook.getSheetAt(0);
}
}
엑셀에는 csv와 달리 header가 존재하지 않아서 데이터의 값을 구하기 위해서는 row와 cell의 index를 가지고 값을 찾아야 한다
결국에는 한 row를 객체의 형태로 저장하고 싶기 때문에
첫 줄을 header라 가정하고 읽어보자
Row row = sheet.getRow(0);
Map<Integer, String> headerMap = new HashMap<>();
int cellCount = row.getPhysicalNumberOfCells();
for (int i = 0; i < cellCount; i++) {
Cell cell = row.getCell(i);
headerMap.put(i, cell.getStringCellValue());
}
엑셀에서 주의해야 할 점은 cell 값의 타입에 꽤나 엄격하다는 것이다
위 코드에서는 cell 값이 string이라 가정하고 값을 읽어왔지만 만약에 cell type이 numeric과 같은 string이 아닌 경우 exception을 발생시키고 있다
그러므로 cell value type 별로 값을 읽어오는 함수를 하나 만들어주자
private String getCellValue(Cell cell) {
switch (cell.getCellType()) {
case NUMERIC:
return String.valueOf(cell.getNumericCellValue();
case STRING:
return cell.getStringCellValue();
case BLANK:
return null;
}
return String.valueOf(cell.getNumericCellValue());
}
위의 타입 이외에도 여러 가지가 있으니 쓰임에 맞게 변형해서 사용하자