자바로 엑셀 읽기 쓰기등을 검색하다보면 수많은 글들이 나오는데 xls, xlsx를 보통 구분하여 생성하는 예제들을 확인할 수 있는데, 저는 어떤 엑셀파일을 올려도 읽도록 처리하고 싶었습니다.
실제로 운용중인 사이트에서도 엑셀을 업로드하여 읽고 DB에 저장하는 로직이 있는데, xls파일을 처리하도록 되어있는데 종종 xlsx로 올려서 문제가 되는 경우가 있었습니다.
이런 문제를 방지하기 위해 개발자 입장에서는 확장자를 체크하여 한가지의 엑셀만 받도록 처리하여도 되지만 사용자 입장에서는 불편 할 수도 있을것이라 생각했습니다.
.xls의 경우 97-03 통합문서로 과거의 엑셀파일이며, .xlsx는 요즘 엑셀문서를 저장하면 생성되는 확장자입니다.
POI라이브러리를 사용하여 읽거나 쓰기를 할때, 확장자에 따라 구분되어 사용되는데, 아래와 같다
.xls | HSSF~ |
.xlsx | XSSF~, SXSSF~(대용량 처리) |
문서를 뜻하는 Workbook, Sheet, Row, Cell 모두 앞에 붙는 클래스명에 따라 구분되는데, 제일 부모 Interface를 사용하여 처리하도록 구성해봤습니다.
.xls, .xlsx 확장자에 따라 구분하여 읽기
간단하게 maven프로젝트를 구성하고 poi라이브러리를 maven에 추가합니다.
pom.xml
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.0</version>
</dependency>
HSSF, XSSF모두 사용하기 때문에 poi, poi-ooxml 모두 같은 버전으로 추가해줍니다.
(버전이 다른경우 에러가 발생합니다.)
App.java
package com.psw.excel;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
/**
* POI LIB
*
*/
public class App {
public static void main(String[] args) {
String path = "C:/whox2/";
String fileName = "test.xlsx";
List<Map<Object, Object>> excelData = readExcel(path, fileName);
// 결과 확인!
for (int i = 0; i < excelData.size(); i++) {
System.out.println(excelData.get(i));
}
}
public static List<Map<Object, Object>> readExcel(String path, String fileName) {
List<Map<Object, Object>> list = new ArrayList<>();
if (path == null || fileName == null) {
return list;
}
FileInputStream is = null;
File excel = new File(path + fileName);
try {
is = new FileInputStream(excel);
Workbook workbook = null;
if (fileName.endsWith(".xls")) {
workbook = new HSSFWorkbook(is);
} else if (fileName.endsWith(".xlsx")) {
workbook = new XSSFWorkbook(is);
}
if (workbook != null) {
int sheets = workbook.getNumberOfSheets();
getSheet(workbook, sheets, list);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return list;
}
public static void getSheet(Workbook workbook, int sheets, List<Map<Object, Object>> list) {
for (int z = 0; z < sheets; z++) {
Sheet sheet = workbook.getSheetAt(z);
int rows = sheet.getLastRowNum();
getRow(sheet, rows, list);
}
}
public static void getRow(Sheet sheet, int rows, List<Map<Object, Object>> list) {
for (int i = 0; i <= rows; i++) {
Row row = sheet.getRow(i);
if (row != null) {
int cells = row.getPhysicalNumberOfCells();
list.add(getCell(row, cells));
}
}
}
public static Map<Object, Object> getCell(Row row, int cells) {
String[] columns = { "column1", "column2", "column3", "column4", "column5", "column6" };
Map<Object, Object> map = new HashMap<>();
for (int j = 0; j < cells; j++) {
if (j >= columns.length) {
break;
}
Cell cell = row.getCell(j);
if (cell != null) {
switch (cell.getCellType()) {
case BLANK:
map.put(columns[j], "");
break;
case STRING:
map.put(columns[j], cell.getStringCellValue());
break;
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
map.put(columns[j], cell.getDateCellValue());
} else {
map.put(columns[j], cell.getNumericCellValue());
}
break;
case ERROR:
map.put(columns[j], cell.getErrorCellValue());
break;
default:
map.put(columns[j], "");
break;
}
}
}
return map;
}
}
각 셀의 타입도 체크하여 최대한 데이터에 맞게 집어넣을 수 있도록 처리해봤습니다.
주의점은 날짜형의 데이터도 NUMERIC으로 인식하는데 DateUtil을 통해 날짜타입인지 구분처리가 가능합니다.
결과 확인
먼저 test.xls 문서입니다.
정상적으로 읽어오는 것을 볼 수 있습니다.
다음은 test.xlsx입니다.
주의사항
이렇게 간단한 xls, xlsx를 읽는 소스를 작성하였지만, HSSF, XSSF의 정확한 내부 내용을 확인해보지 못했고, POI에서도 개발할때 굳이 구분을 지은 이유는 차이점이 있기 때문일것입니다. 상속을 통해 내부적으로 처리하는 로직이 다를 수 있고 이때문에 추후 문제가 발생할 수 있으니, 별도의 메소드를 사용시에는 많은 테스트가 필요할 수 있습니다.
'JAVA' 카테고리의 다른 글
JAVA - Jsch를 활용한 SSH 연결, 명령어, 파일전송 (0) | 2021.02.02 |
---|---|
JAVA - Collection(List, Map) 컬렉션 프레임워크 (0) | 2021.01.08 |
Netty Client 튜토리얼 - 04 (서버에서 응답이 없는 경우 일정 시간 후 종료하기) (0) | 2020.12.04 |
Netty Client 튜토리얼 - 03 (서버에 연결이 안 될 경우 재시도하기) (0) | 2020.12.04 |
JAVA - Jsch를 활용한 sftp 전송 처리하기 (0) | 2020.12.02 |