Как прочитать числовое значение из файла Excel с помощью Spring Batch Excel
Я читаю значения из .xlsx, используя spring batch excel и POI. Я вижу, что числовые значения печатаются в формате, отличном от исходного значения в .xlsx
Пожалуйста, предложите мне, как распечатать значения как в файле .xlsx. Ниже приведены подробности.
В моем Excel значения следующие
Значения печатаются, как показано ниже
Мой код такой, как показано ниже
public ItemReader<DataObject> fileItemReader(InputStream inputStream){
PoiItemReader<DataObject> reader = new PoiItemReader<DataObject>();
reader.setLinesToSkip(1);
reader.setResource(new InputStreamResource(DataObject));
reader.setRowMapper(excelRowMapper());
reader.open(new ExecutionContext());
return reader;
}
private RowMapper<DataObject> excelRowMapper() {
return new MyRowMapper();
}
public class MyRowMapper implements RowMapper<DataObject> {
@Override
public DataRecord mapRow(RowSet rowSet) throws Exception {
DataObject dataObj = new DataObject();
dataObj.setFieldOne(rowSet.getColumnValue(0));
dataObj.setFieldTwo(rowSet.getColumnValue(1));
dataObj.setFieldThree(rowSet.getColumnValue(2));
dataObj.setFieldFour(rowSet.getColumnValue(3));
return dataObj;
}
}
2 ответа
У меня была такая же проблема, и ее корнем является класс org.springframework.batch.item.excel.poi.PoiSheet внутри PoiItemReader. Проблема возникает в методе
public String[] getRow(final int rowNumber)
где он получает объект org.apache.poi.ss.usermodel.Row и преобразует его в массив строк после определения типа каждого столбца в строке. В этом методе у нас есть код:
switch (cellType) {
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
Date date = cell.getDateCellValue();
cells.add(String.valueOf(date.getTime()));
} else {
cells.add(String.valueOf(cell.getNumericCellValue()));
}
break;
case BOOLEAN:
cells.add(String.valueOf(cell.getBooleanCellValue()));
break;
case STRING:
case BLANK:
cells.add(cell.getStringCellValue());
break;
case ERROR:
cells.add(FormulaError.forInt(cell.getErrorCellValue()).getString());
break;
default:
throw new IllegalArgumentException("Cannot handle cells of type '" + cell.getCellTypeEnum() + "'");
}
В котором обработка ячейки, обозначенной как NUMERIC, есть. В этой строке значение ячейки преобразуется в двойное (
cell.getNumericCellValue()
), и этот тип double преобразуется в String(). Проблема возникает в
String.valueOf()
, который генерирует научную нотацию, если число слишком велико (>=10000000) или слишком мало (<0,001), и помещает «.0» в целочисленные значения.
Как альтернатива линии
cells.add(String.valueOf(cell.getNumericCellValue()))
, вы могли бы использовать
DataFormatter formatter = new DataFormatter();
cells.add(formatter.formatCellValue(cell));
который вернет вам точные значения ячеек в виде строки. Однако это также означает, что ваши десятичные числа будут зависеть от локали (вы получите строку «2,5» из документа, сохраненного в Excel, настроенном для Великобритании или Индии, и строку «2,5» из Франции или Бразилии).
Чтобы избежать этой зависимости, мы можем использовать решение, представленное на /questions/18304818/kak-napechatat-dvojnoe-znachenie-bez-nauchnoj-notatsii-s-ispolzovaniem-java/18304835#18304835:
DecimalFormat df = new DecimalFormat("0", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
df.setMaximumFractionDigits(340);
cells.add(df.format(cell.getNumericCellValue()));
Это преобразует ячейку в двойную, а затем отформатирует ее в соответствии с английским шаблоном без научного обозначения или добавления «.0» к целым числам.
Моя реализация CustomPoiSheet (небольшая адаптация оригинального PoiSheet) была:
class CustomPoiSheet implements Sheet {
protected final org.apache.poi.ss.usermodel.Sheet delegate;
private final int numberOfRows;
private final String name;
private FormulaEvaluator evaluator;
/**
* Constructor which takes the delegate sheet.
*
* @param delegate the apache POI sheet
*/
CustomPoiSheet(final org.apache.poi.ss.usermodel.Sheet delegate) {
super();
this.delegate = delegate;
this.numberOfRows = this.delegate.getLastRowNum() + 1;
this.name=this.delegate.getSheetName();
}
/**
* {@inheritDoc}
*/
@Override
public int getNumberOfRows() {
return this.numberOfRows;
}
/**
* {@inheritDoc}
*/
@Override
public String getName() {
return this.name;
}
/**
* {@inheritDoc}
*/
@Override
public String[] getRow(final int rowNumber) {
final Row row = this.delegate.getRow(rowNumber);
if (row == null) {
return null;
}
final List<String> cells = new LinkedList<>();
final int numberOfColumns = row.getLastCellNum();
for (int i = 0; i < numberOfColumns; i++) {
Cell cell = row.getCell(i);
CellType cellType = cell.getCellType();
if (cellType == CellType.FORMULA) {
FormulaEvaluator evaluator = getFormulaEvaluator();
if (evaluator == null) {
cells.add(cell.getCellFormula());
} else {
cellType = evaluator.evaluateFormulaCell(cell);
}
}
switch (cellType) {
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
Date date = cell.getDateCellValue();
cells.add(String.valueOf(date.getTime()));
} else {
// Returns numeric value the closer possible to it's value and shown string, only formatting to english format
// It will result in an integer string (without decimal places) if the value is a integer, and will result
// on the double string without trailing zeros. It also suppress scientific notation
// Regards to https://stackoverflow.com/a/25307973/9184574
DecimalFormat df = new DecimalFormat("0", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
df.setMaximumFractionDigits(340);
cells.add(df.format(cell.getNumericCellValue()));
//DataFormatter formatter = new DataFormatter();
//cells.add(formatter.formatCellValue(cell));
//cells.add(String.valueOf(cell.getNumericCellValue()));
}
break;
case BOOLEAN:
cells.add(String.valueOf(cell.getBooleanCellValue()));
break;
case STRING:
case BLANK:
cells.add(cell.getStringCellValue());
break;
case ERROR:
cells.add(FormulaError.forInt(cell.getErrorCellValue()).getString());
break;
default:
throw new IllegalArgumentException("Cannot handle cells of type '" + cell.getCellTypeEnum() + "'");
}
}
return cells.toArray(new String[0]);
}
private FormulaEvaluator getFormulaEvaluator() {
if (this.evaluator == null) {
this.evaluator = delegate.getWorkbook().getCreationHelper().createFormulaEvaluator();
}
return this.evaluator;
}
}
И моя реализация CustomPoiItemReader (небольшая адаптация оригинального PoiItemReader), вызывающая CustomPoiSheet:
public class CustomPoiItemReader<T> extends AbstractExcelItemReader<T> {
private Workbook workbook;
@Override
protected Sheet getSheet(final int sheet) {
return new CustomPoiSheet(this.workbook.getSheetAt(sheet));
}
public CustomPoiItemReader(){
super();
}
@Override
protected int getNumberOfSheets() {
return this.workbook.getNumberOfSheets();
}
@Override
protected void doClose() throws Exception {
super.doClose();
if (this.workbook != null) {
this.workbook.close();
}
this.workbook=null;
}
/**
* Open the underlying file using the {@code WorkbookFactory}. We keep track of the used {@code InputStream} so that
* it can be closed cleanly on the end of reading the file. This to be able to release the resources used by
* Apache POI.
*
* @param inputStream the {@code InputStream} pointing to the Excel file.
* @throws Exception is thrown for any errors.
*/
@Override
protected void openExcelFile(final InputStream inputStream) throws Exception {
this.workbook = WorkbookFactory.create(inputStream);
this.workbook.setMissingCellPolicy(Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
}
}
просто измените свой код таким образом при чтении данных из Excel.
dataObj.setField(Float.valueOf(rowSet.getColumnValue(idx)).intValue();
это работает только для столбцов A,B,C