Simplify

자바 프로그래밍으로 엑셀 파일 읽기 - How to read Excel file with Java programming (with POI) 본문

Other/Java

자바 프로그래밍으로 엑셀 파일 읽기 - How to read Excel file with Java programming (with POI)

Simplify - Jonghun 2020. 2. 24. 16:27

서버 측 프로그래밍을 하다 보면, 특히나 웹 프로그래밍을 하다보면, Excel 파일을 읽고 쓰는 일이 빈번합니다. 예를 들어 화면에 출력된 내용을 Excel 파일로 내보내기(export)한다거나, 한꺼번에 다량의 항목을 등록하려 할 때에 Excel 파일로 등록하기(import) 등의 기능이 이에 해당합니다. 물론 다양한 방법으로 Excel 파일을 읽고 쓰는 것이 가능하지만, 가장 흔히 사용되고 편리한 것은 POI 라는 것을 이용하는 것입니다. 사실 과거에 - 아주 오래전 - 는 엑셀 파일을 테스트 파일로 읽어들이면, xml 파일과 유사한 형태를 띄고 있어 쉽게 읽고 쓰는 것이 가능했지만, 최근에는 보안상의 이유인지 이 마저도 제대로 보이지 않고, 특히 xlsx 파일은 똑바로 읽을 방법이 없습니다. 

 

간단하지만 가볍게 봐 두면 한번 쯤은 사용하게 되는 기능인 것 같습니다. 코드부터 보여드리고 몇 가지 팁을 적겠습니다. 

 

package excel;
 
import java.io.FileInputStream;
 
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 
public class ExcelRead {
 
    public static void main(String[] args) {
 
        try {
            FileInputStream file = new FileInputStream("D:/tmp/upload/right_excel/test.xlsx");
            XSSFWorkbook workbook = new XSSFWorkbook(file);
 
            int rowindex=0;
            int columnindex=0;
            //시트 수 (첫번째에만 존재하므로 0을 준다)
            //만약 각 시트를 읽기위해서는 FOR문을 한번더 돌려준다
            XSSFSheet sheet=workbook.getSheetAt(0);
            //행의 수
            int rows=sheet.getPhysicalNumberOfRows();
            for(rowindex=0;rowindex<rows;rowindex++){
                //행을읽는다
                XSSFRow row=sheet.getRow(rowindex);
                if(row !=null){
                    //셀의 수
                    int cells=row.getPhysicalNumberOfCells();
                    for(columnindex=0; columnindex<=cells; columnindex++){
                        //셀값을 읽는다
                        XSSFCell cell=row.getCell(columnindex);
                        String value="";
                        //셀이 빈값일경우를 위한 널체크
                        if(cell==null){
                            continue;
                        }else{
                            //타입별로 내용 읽기
                            switch (cell.getCellType()){
                            case XSSFCell.CELL_TYPE_FORMULA:
                                value=cell.getCellFormula();
                                break;
                            case XSSFCell.CELL_TYPE_NUMERIC:
                                value=cell.getNumericCellValue()+"";
                                break;
                            case XSSFCell.CELL_TYPE_STRING:
                                value=cell.getStringCellValue()+"";
                                break;
                            case XSSFCell.CELL_TYPE_BLANK:
                                value=cell.getBooleanCellValue()+"";
                                break;
                            case XSSFCell.CELL_TYPE_ERROR:
                                value=cell.getErrorCellValue()+"";
                                break;
                            }
                        }
                        System.out.println(rowindex+"번 행 : "+columnindex+"번 열 값은: "+value);
                    }
 
                }
            }
 
        }catch(Exception e) {
            e.printStackTrace();
        }
 
    }
 
}

편의 상, 아래 출처& 참고자료에 적힌 블로그에서 코드 블럭을 가져왔습니다. 저 역시 유사하게 사용하있고 간단한 것들은 이 정도로 충분합니다만, 다음과 같은 부분에 대해서 추가적으로 팁을 드리고자 합니다. 

 

xls * xlsx 파일 두 가지 모두 처리하기 

FileInputStream fileInputStream = new FileInputStream(templateFile);
if (templateFile.getAbsolutePath().endsWith("xlsx")) {
    workbook = new XSSFWorkbook(fileInputStream);
} else if (templateFile.getAbsolutePath().endsWith("xls")) {
    workbook = new HSSFWorkbook(fileInputStream);
}

파일 확장자에 따라서 분기합니다. 이렇게 하면 위 소스에서 몇 가지 부분이 에러가 발생할 텐데, 그 상위 클래스로 잡하주면 됩니다. 예를 들어 XSSFCell 은 그냥 Cell 로 잡아주면 되고, Cell Type을 분기하는 부분은 아래와 같이 분기하면 됩니다.

switch (cell.getCellType()) {
	case FORMULA:
    
    ...

 

첫 줄이 비어있는, 중간부터 쓰여있는 표 형태를 읽는 경우

int lastRowIndex = sheet.getLastRowNum();
	for (rowIndex = 0; rowIndex <= lastRowIndex; rowIndex++) {
		int realRowIndex = rowIndex + sheet.getFirstRowNum();
		Row row = sheet.getRow(realRowIndex);
        
        ...

위와 같은 형태로 처리하면 중간에 덩그러니 쓰여있는 형태의 파일도 제대로 읽어올 수 있습니다. 

 

getLastRowNum() 함수는 그야말로 유효한 값이 있는 마지막 행 번호를 받아옵니다. 그런데, 0번 Row 부터 처리하면 빈 row 가 나올 수 있기 때문에 첫 줄은 getFirstRowNum() 으로 가져오는 것이 맞습니다. 이렇게 하면 빈줄 읽어와서 처리하는 것 대신에, 유효한 첫 줄부터 유효한 마지막 줄 까지를 읽을 수 있습니다. 

 

셀 읽는 범위 처리

위와 같이 getPhysicalNumberOfCells() 를 호출해서 얻는 숫자는 유효한 컬럼 수를 return 합니다. 만얀 A, B 컬럼 정도가 비어있다면, 위 형태로는 마지막까지 읽지 못하고 마지막 두 컬럼을 읽지 못합니다. 따라서 아래와 같이 처리합니다. 

nt lastCellIndex = row.getLastCellNum();
	for (columnIndex = 0; columnIndex <= lastCellIndex; columnIndex++) {						
		Cell cell = row.getCell(columnIndex);
		Object value = null;
        
        if (cell != null) {
        
        ...

마지막 cell 번호를 getLastCellNum() 으로 얻어옵니다. 이렇게 하면 유효한 값이 있는 마지막 셀을 얻어오므로, 0번 컬럼부터 체크하면서 null 여부를 판단하면 마지막 셀 까지 다 읽어낼 수 있습니다. 

 

 

여러가지 트릭이나 편법이 있고, 더 좋은 방법들이 있는 줄 알고 있습니다만, 위에서 설명한 세 가지는 유용한 정보로 보여 공유 차원에서 올립니다. 적고 나니 getFirstCellNum() 이 있는지 궁금하네요. (아마 있을 듯 싶습니다..)

엑셀을 읽을 때 정해진 틀 내에서 처리하도록 가이드하는 것도 중요하므로 보통 '다량 건 일괄 등록'과 같은 기능을 개발하는 경우에는 templete 파일을 제공하고 이 틀에 맞추어 등록하도록 가이드합니다. 

 

그래도 혹시 모르고, 조금 더 자유롭게 등록하게 하기 위해서, 또 다양한 파이를 그대로 읽기 위해서는 위와 같이 작업해 두는 것이 좋습니다. 

 

 

 

출처 & 참고자료 

https://huskdoll.tistory.com/m/890

Comments