개요
기존에 Apache Poi 라이브러리를 활용해 업로드된 엑셀 데이터를 읽은 뒤 처리하는 코드가 있었는데 fastexcel 라이브러리로 생성된 엑셀을 읽어오지 못하는 문제가 발생했습니다.
이에 따라 fastexcel 라이브러리를 활용해 엑셀 데이터를 읽어오는 코드를 구현했고 테스트한 결과 모든 엑셀을 읽어올 수 있었습니다.
fastexcel 라이브러리 관련해서는 아래 글을 참고해주세요.
https://jaimemin.tistory.com/2191
[SpringBoot + Fastexcel] 대용량 엑셀 생성 및 다운로드
개요 여태까지 엑셀 생성 및 다운로드 기능을 구현할 때 Apache Poi 라이브러리를 사용했었고 이와 관련하여 게시글을 여러 번 남겼습니다. https://jaimemin.tistory.com/2069 [SpringBoot] 대용량 엑셀 파일 생
jaimemin.tistory.com
코드
모든 엑셀 파일에 대해서 읽어올 수 있어야하므로 자바 Generic을 적용하여 코드를 작성했습니다.
코드가 정상적으로 작동하기 위해서는 아래의 fastexcel-reader 라이브러리가 추가되어 있어야 합니다!
<dependency>
<groupId>org.dhatim</groupId>
<artifactId>fastexcel-reader</artifactId>
<version>0.12.3</version>
</dependency>
FastExcelReader.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.fasterxml.jackson.databind.DeserializationFeature; | |
import com.fasterxml.jackson.databind.MapperFeature; | |
import com.fasterxml.jackson.databind.ObjectMapper; | |
import lombok.extern.slf4j.Slf4j; | |
import org.apache.commons.lang3.ObjectUtils; | |
import org.dhatim.fastexcel.reader.Cell; | |
import org.dhatim.fastexcel.reader.ReadableWorkbook; | |
import org.dhatim.fastexcel.reader.Row; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.lang.reflect.Type; | |
import java.math.BigDecimal; | |
import java.time.LocalDateTime; | |
import java.util.*; | |
import java.util.concurrent.atomic.AtomicInteger; | |
import java.util.stream.Stream; | |
@Slf4j | |
public class FastExcelReader { | |
public static <E> List<E> read(InputStream is, ExcelReadOption excelReadOption, Class<E> classType) { | |
List<E> result = new ArrayList<>(); | |
if (ObjectUtils.isEmpty(is)) { | |
return result; | |
} | |
try (ReadableWorkbook wb = new ReadableWorkbook(is)) { | |
ObjectMapper objectMapper = new ObjectMapper(); | |
objectMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false); | |
objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); | |
final Map<String, Type> cells = excelReadOption.getOutputColumns(); | |
int startRow = excelReadOption.getStartRow() < 0 ? 0 : excelReadOption.getStartRow(); | |
wb.getSheets().forEach(sheet -> { | |
try (Stream<Row> rows = sheet.openStream()) { | |
/** | |
* 각 sheet당 첫 번째 row는 header | |
*/ | |
rows.skip(startRow).forEach(row -> { | |
AtomicInteger index = new AtomicInteger(); | |
/** | |
* 각 row마다의 값을 저장할 맵 객체 저장되는 형식은 다음과 같다. put("A", "이름"); put("B", "게임명"); | |
*/ | |
Map<String, Object> rowData = new LinkedHashMap<>(); | |
cells.forEach((key, val) -> { | |
Object refVal; | |
if (val.equals(Integer.class) | |
|| val.equals(Double.class) | |
|| val.equals(Long.class)) { | |
BigDecimal bigDecimal = row.getCellAsNumber(index.getAndIncrement()).orElse(null); | |
refVal = getRefVal(val, bigDecimal); | |
} else if (val.equals(LocalDateTime.class) || val.equals(Date.class)) { | |
refVal = row.getCellAsDate(index.getAndIncrement()).orElse(null); | |
} else if (val.equals(Boolean.class)) { | |
refVal = row.getCellAsBoolean(index.getAndIncrement()).orElse(null); | |
} else { | |
Cell cell = row.getCell(index.getAndIncrement()); | |
refVal = cell.getValue() + ""; | |
// [FastExcelReader.read] ERROR Wrong cell type NUMBER, wanted STRING | |
// refVal = row.getCellAsString(index.getAndIncrement()).orElse("-"); | |
} | |
rowData.put(key, refVal); | |
}); | |
E o = objectMapper.convertValue(rowData, classType); | |
result.add(o); | |
}); | |
} catch (IOException e) { | |
log.error("[FastExcelReader.read] ERROR {}", e.getMessage()); | |
} | |
}); | |
return result; | |
} catch (Exception e) { | |
log.error("[FastExcelReader.read] ERROR {}", e.getMessage()); | |
} | |
return result; | |
} | |
private static Object getRefVal(Type val, BigDecimal bigDecimal) { | |
Object refVal; | |
if (bigDecimal == null) { | |
refVal = 0; | |
} else { | |
if (val.equals(Integer.class)) { | |
refVal = bigDecimal.intValue(); | |
} else if (val.equals(Double.class)) { | |
refVal = bigDecimal.doubleValue(); | |
} else if (val.equals(Long.class)) { | |
refVal = bigDecimal.longValue(); | |
} else { | |
refVal = 0; | |
} | |
} | |
return refVal; | |
} | |
} |
ExcelReadOption.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Data | |
@Builder | |
public class ExcelReadOption { | |
/** | |
* 추출할 컬럼 명 | |
*/ | |
private Map<String, Type> outputColumns; | |
/** | |
* 추출을 시작할 행 번호 | |
*/ | |
private int startRow; | |
} |
예시 코드
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 칼럼이 총 6개 존재하는 엑셀을 읽어오는 | |
private List<Sample> convertMultipartFileToExcelList(MultipartFile excelFile) { | |
ExcelReadOption option = ExcelReadOption.builder() | |
.startRow(1) | |
.outputColumns(Sample.getColumnMap()) | |
.build(); | |
List<Sample> inputList = new ArrayList<>(); | |
try { | |
inputList = FastExcelReader.read(excelFile.getInputStream(), option, Sample.class); | |
} catch (Exception e) { | |
log.error("[convertMultipartFileToExcelList] ERROR {}", e.getMessage()); | |
} | |
return inputList; | |
} | |
@Getter | |
@Setter | |
public class Sample { | |
private String a; | |
private String b; | |
private String c; | |
private String d; | |
private String e; | |
private String f; | |
public Sample() { | |
} | |
public static Map<String, Type> getColumnMap() { | |
Map<String, Type> map = new LinkedHashMap<>(); | |
map.put("a", String.class); | |
map.put("b", String.class); | |
map.put("c", String.class); | |
map.put("d", String.class); | |
map.put("e", String.class); | |
map.put("f", String.class); | |
return map; | |
} | |
} |
반응형
'[DEV] 기록' 카테고리의 다른 글
COS Pro 1급 Java, C++, C 합격 간단 후기 (7) | 2022.10.23 |
---|---|
[SpringBoot 2.X.X] No beans of 'AuthenticationManager' type found. (0) | 2022.10.20 |
[SpringBoot] Prometheus, Grafana 연동하는 방법 (0) | 2022.09.01 |
[SpringBoot] Failed to start bean documentationPluginsBootstrapper (0) | 2022.08.31 |
[SpringBoot] Prometheus 연동시 "INVALID" is not a valid start token (0) | 2022.08.31 |