【问题标题】:How to read numeric value from excel file using spring batch excel如何使用spring batch excel从excel文件中读取数值
【发布时间】:2021-04-29 06:36:49
【问题描述】:

我正在使用 spring batch excel 和 POI 从 .xlsx 读取值。我看到数值的打印格式与 .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;

        }
    }

【问题讨论】:

  • 查看数字长度是否大于 7,然后字母和长度会附加到数字上,例如 12345678 变为 12345678E8
  • 我也面临同样的问题。如果您找到解决方案,请告诉我。

标签: spring spring-boot apache-poi spring-batch-excel


【解决方案1】:

我遇到了同样的问题,它的根源是 PoiItemReader 中的 org.springframework.batch.item.excel.poi.PoiSheet 类。 问题发生在方法 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 的单元格的处理是cells.add(String.valueOf(cell.getNumericCellValue()))。在这一行中,单元格值被转换为双精度(cell.getNumericCellValue()),而这个双精度被转换为字符串(String.valueOf())。问题发生在 String.valueOf() 方法中,如果数字太大 (>=10000000) 或太小 (

作为cells.add(String.valueOf(cell.getNumericCellValue())) 行的替代方法,您可以使用

DataFormatter formatter = new DataFormatter();
cells.add(formatter.formatCellValue(cell));

这会将单元格的确切值作为字符串返回给您。但是,这也意味着您的十进制数字将取决于区域设置(您将收到来自为英国或印度配置的 Excel 中保存的文档中的字符串“2.5”和来自法国或巴西的字符串“2,5”)。

为了避免这种依赖,我们可以使用https://stackoverflow.com/a/25307973/9184574上提出的解决方案:

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;
    }
}

以及我调用 CustomPoiSheet 的 CustomPoiItemReader(对原始 PoiItemReader 的小改编)的实现:

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);
    }

}

【讨论】:

    【解决方案2】:

    在从 excel 读取数据时,只需像这样更改您的代码。

    dataObj.setField(Float.valueOf(rowSet.getColumnValue(idx)).intValue();

    这仅适用于 A、B、C 列

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-21
      • 1970-01-01
      • 1970-01-01
      • 2023-01-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多