9

Java 8 在这里使用 Apache POI 4.1 将 Excel (XLSX) 文件加载到内存中,并将 Java bean/POJO 列表写回新的 Excel 文件。

对我来说,一个 Excel 文件(至少是我正在使用的文件)实际上是一个 POJO 列表,每一行是 POJO 的不同实例,每一列是该实例的不同字段值。观察:

在此处输入图像描述

在这里,我可能有一个名为 的 POJO Car,上面的示例电子表格是List<Car>

@Getter
@Setter
public class Car {

  private String manufacturer;
  private String model;
  private String color;
  private String year;
  private BigDecimal price;

}

所以我有功能代码,它将一个 Excel 文件(“ new-cars.xlsx”)读入一个List<Car>,处理该列表,然后将处理后的列表写回一个输出文件,例如,“ processed-cars.xlsx”:

// 1. Load excel file into a List<Car>
InputStream inp = new FileInputStream("new-cars.xlsx");
Workbook workbook = WorkbookFactory.create(inp);
Iterator<Row> iterator = workbook.getSheetAt(0).iterator();

List<Car> carsInventory = new ArrayList<>();
while (iterator.hasNext()) {

    Car car = new Car();

    Row currentRow = iterator.next();

    // don't read the header
    if (currentRow.getRowNum() == 0) {
        continue;
    }

    Iterator<Cell> cellIterator = currentRow.iterator();

    while (cellIterator.hasNext()) {

        Cell currentCell = cellIterator.next();
        CellAddress address = currentCell.getAddress();

        if (0 == address.getColumn()) {
            // 1st col is "Manufacturer"
            car.setManufacturer(currentCell.getStringCellValue());
        } else if (1 == address.getColumn()) {
            // 2nd col is "Model"
            car.setModel(currentCell.getStringCellValue());
        } else if (2 == address.getColumn()) {
            // 3rd col is "Color"
            car.setColor(currentCell.getStringCellValue());
        } else if (3 == address.getColumn()) {
            // 4th col is "Year"
            car.setYear(currentCell.getStringCellValue());
        } else if (4 == address.getColumn()) {
            // 5th col is "Price"
            car.setPrice(BigDecimal.valueOf(currentCell.getNumericCellValue()));
        }

    }

    carsInventory.add(car);

}

// 2. Process the list of Cars; doesn't matter what this does
List<Car> processedInventory = processInventory(carsInventory);

// 3. Output to "processed-cars.xlsx"
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("Processed Inventory");
int rowNum = 0;

// create headers
Row headerRow = sheet.createRow(rowNum);
headerRow.createCell(0).setCellValue("Manufacturer");
headerRow.createCell(1).setCellValue("Model");
headerRow.createCell(2).setCellValue("Color");
headerRow.createCell(3).setCellValue("Year");
headerRow.createCell(4).setCellValue("Price");

rowNum++;

// rip through the cars list and convert each one into a subsequent row
for (Car processedCar : processedInventory) {

    Row nextRow = sheet.createRow(rowNum);

    nextRow.createCell(0).setCellValue(processedCar.getManufacturer());
    nextRow.createCell(1).setCellValue(processedCar.getModel());
    nextRow.createCell(2).setCellValue(processedCar.getColor());
    nextRow.createCell(3).setCellValue(processedCar.getYear());
    nextRow.createCell(4).setCellValue(processedCar.getPrice().doubleValue());

    rowNum++;

}

FileOutputStream fos = new FileOutputStream("processed-cars.xlsx");
workbook.write(fos);

workbook.close();

虽然这可行,但对我来说它看起来真的很丑/讨厌。多年来我一直使用 JSON 映射器(Jackson、GSON 等)、XML 映射器(XStream)和 OR/M 工具(Hibernate),我突然想到 POI 的 API(或其他一些库)可能会提供“映射器” -esque ”解决方案,它允许我以最少的代码和最大的优雅将 Excel 数据映射/绑定到 POJO 列表。但是,我在任何地方都找不到任何此类功能。也许这是因为它不存在,或者我只是没有搜索正确的关键字。

理想情况下,大致如下:

// Annotate the fields with something that POI (or whatever tool) can pick up
@Getter
@Setter
public class Car {

  @ExcelColumn(name = "Manufacturer", col = 0)
  private String manufacturer;

  @ExcelColumn(name = "Model", col = 1)
  private String model;

  @ExcelColumn(name = "Color", col = 2)
  private String color;

  @ExcelColumn(name = "Year", col = 3)
  private String year;

  @ExcelColumn(name = "Price", col = 4)
  private BigDecimal price;

}

// 2. Now load the Excel into a List<Car>
InputStream inp = new FileInputStream("new-cars.xlsx");
List<Car> carsInventory = WorkbookFactory.create(inp).buildList(Car.class);

// 3. Process the list
List<Car> processedInventory = processInventory(carsInventory);

//4. Write to a new file
WorkbookFactory.write(processInventory, "processed-cars.xlsx");

POI-land中是否存在类似的东西?还是我坚持我所得到的?

4

5 回答 5

18

到目前为止,Apache POI 还没有这样的功能。您可以检查一些外部库。我提供以下几个库。

https://github.com/ozlerhakan/poiji

该库在 mvnrepository 中可用,链接如下。该库仅提供一种绑定方式,例如从 excel 表到 java pojo。

https://mvnrepository.com/artifact/com.github.ozlerhakan/poiji/2.2.0

根据上面,你可以做这样的事情。

public class Employee {

    @ExcelRow                  
    private int rowIndex;

    @ExcelCell(0)                
    private long employeeId;  

    @ExcelCell(1)
    private String name;

    @ExcelCell(2)
    private String surname;

    @ExcelCell(3)
    private int age;
}

要从 excel 表中获取信息到 java 对象,您必须按照以下方式进行。

List<Employee> employees = Poiji.fromExcel(new File("employees.xls"), Employee.class);

还有另一个库可以同时做 excel to java 和 java to excel 之类的事情。我在链接下方提供。

https://github.com/millij/poi-object-mapper

根据上面的库,你可以做这样的事情。

@Sheet
public class Employee {

    @SheetColumn("Age")
    private Integer age;

    @SheetColumn("Name")
    public String getName() {
        return name;
    }

}

要从 xlsx 文件中获取数据,您必须这样编写。

final File xlsxFile = new File("<path_to_file>");
final XlsReader reader = new XlsReader();
List<Employee> employees = reader.read(Employee.class, xlsxFile);

要将数据写入 excel 工作表,您必须这样做。

List<Employee> employees = new ArrayList<Employee>();
employees.add(new Employee("1", "foo", 12, "MALE", 1.68));
SpreadsheetWriter writer = new SpreadsheetWriter("<output_file_path>");
writer.addSheet(Employee.class, employees);
writer.write();

您必须针对您的用例评估这两个库。

于 2019-11-21T18:22:02.253 回答
4

我会考虑编写自己的apache poi映射POJO器包,而不是简单地搜索任何可用的包。这样做你可以更灵活地扩展功能,因为你知道它是如何工作的,而无需深入研究其他人编写的代码,这些代码被大量划分为类和方法。试图理解这样的代码可能真的很困难。更不用说知道在哪里放置自己想要的扩展。

首先,这是一个PoiPOJO到目前为止只包含两个类的包。PoiPOJOUtils它提供了两种静态方法。一个sheetToPOJO和一个pojoToSheet。那么这ExcelColumn是一个可在类中使用的Annotation接口。POJO

PoiPOJOUtils.java

package PoiPOJO;

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellUtil;
import java.util.*;
import java.lang.reflect.*;

public class PoiPOJOUtils {

 public static <T> List<T> sheetToPOJO(Sheet sheet, Class<T> beanClass) throws Exception {

  DataFormatter formatter = new DataFormatter(java.util.Locale.US);
  FormulaEvaluator evaluator = sheet.getWorkbook().getCreationHelper().createFormulaEvaluator();

  int headerRowNum = sheet.getFirstRowNum();

  // collecting the column headers as a Map of header names to column indexes
  Map<Integer, String> colHeaders = new HashMap<Integer, String>();
  Row row = sheet.getRow(headerRowNum);
  for (Cell cell : row) {
   int colIdx = cell.getColumnIndex();
   String value = formatter.formatCellValue(cell, evaluator);
   colHeaders.put(colIdx, value);
  }

  // collecting the content rows
  List<T> result = new ArrayList<T>();
  String cellValue = "";
  java.util.Date date = null;
  Double num = null;
  for (int r = headerRowNum + 1; r <= sheet.getLastRowNum(); r++) {
   row = sheet.getRow(r); if (row == null) row = sheet.createRow(r);
   T bean = beanClass.getDeclaredConstructor().newInstance();

   for (Map.Entry<Integer, String> entry : colHeaders.entrySet()) {
    int colIdx = entry.getKey();
    Cell cell = row.getCell(colIdx); if (cell == null) cell = row.createCell(colIdx);
    cellValue = formatter.formatCellValue(cell, evaluator); // string values and formatted numbers
    // make some differences for numeric or formula content
    date = null;
    num = null;
    if (cell.getCellType() == CellType.NUMERIC) {
     if (DateUtil.isCellDateFormatted(cell)) { // date
      date = cell.getDateCellValue();
     } else { // other numbers
      num = cell.getNumericCellValue();
     }
    } else if (cell.getCellType() == CellType.FORMULA) {
     // if formula evaluates to numeric
     if (evaluator.evaluateFormulaCell(cell) == CellType.NUMERIC) {
      if (DateUtil.isCellDateFormatted(cell)) { // date
       date = cell.getDateCellValue();
      } else { // other numbers
       num = cell.getNumericCellValue();
      }
     }
    }

    // fill the bean
    for (Field f : beanClass.getDeclaredFields()) {
     if (!f.isAnnotationPresent(ExcelColumn.class)) {
      continue;
     }
     ExcelColumn ec = f.getAnnotation(ExcelColumn.class);
     if(entry.getValue().equals(ec.name())) {
      f.setAccessible(true);
      if (f.getType() == String.class) {
       f.set(bean, cellValue);
      } else if (f.getType() == Double.class) {
       f.set(bean, num);
      } else if (f.getType() == java.util.Date.class) {
       f.set(bean, date);
      } else { // this is for all other; Integer, Boolean, ...
       if (!"".equals(cellValue)) {
        Method valueOf = f.getType().getDeclaredMethod("valueOf", String.class);
        f.set(bean, valueOf.invoke(f.getType(), cellValue));
       }
      }
     }
    } 
   }
   result.add(bean);
  }

  return result;

 }

 public static <T> void pojoToSheet(Sheet sheet, List<T> rows) throws Exception {
  if (rows.size() > 0) { 
   Row row = null;
   Cell cell = null;
   int r = 0;
   int c = 0;
   int colCount = 0;
   Map<String, Object> properties = null;
   DataFormat dataFormat = sheet.getWorkbook().createDataFormat();

   Class beanClass = rows.get(0).getClass();

   // header row
   row = sheet.createRow(r++);
   for (Field f : beanClass.getDeclaredFields()) {
    if (!f.isAnnotationPresent(ExcelColumn.class)) {
     continue;
    }
    ExcelColumn ec = f.getAnnotation(ExcelColumn.class);
    cell = row.createCell(c++);
    // do formatting the header row
    properties = new HashMap<String, Object>();
    properties.put(CellUtil.FILL_PATTERN, FillPatternType.SOLID_FOREGROUND);
    properties.put(CellUtil.FILL_FOREGROUND_COLOR, IndexedColors.GREY_25_PERCENT.getIndex());
    CellUtil.setCellStyleProperties(cell, properties);
    cell.setCellValue(ec.name());
   }

   colCount = c;

   // contents
   for (T bean : rows) {
    c = 0;
    row = sheet.createRow(r++);
    for (Field f : beanClass.getDeclaredFields()) {
     cell = row.createCell(c++);
     if (!f.isAnnotationPresent(ExcelColumn.class)) {
      continue;
     }
     ExcelColumn ec = f.getAnnotation(ExcelColumn.class);
     // do number formatting the contents
     String numberFormat = ec.numberFormat();
     properties = new HashMap<String, Object>();
     properties.put(CellUtil.DATA_FORMAT, dataFormat.getFormat(numberFormat));
     CellUtil.setCellStyleProperties(cell, properties);

     f.setAccessible(true);
     Object value = f.get(bean);
     if (value != null) {
      if (value instanceof String) {
       cell.setCellValue((String)value);
      } else if (value instanceof Double) {
       cell.setCellValue((Double)value);
      } else if (value instanceof Integer) {
       cell.setCellValue((Integer)value);
      } else if (value instanceof java.util.Date) {
       cell.setCellValue((java.util.Date)value);
      } else if (value instanceof Boolean) {
       cell.setCellValue((Boolean)value);
      }
     }
    }
   }

   // auto size columns
   for (int col = 0; col < colCount; col++) {
    sheet.autoSizeColumn(col);
   }
  }
 }

}

ExcelColumn.java

package PoiPOJO;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelColumn {
 String name();
 String numberFormat() default "General";
} 

这可以用来然后有...

Car.java

import PoiPOJO.ExcelColumn;

public class Car {

 @ExcelColumn(name = "Manufacturer")
 public String manufacturer;

 @ExcelColumn(name = "Model")
 public String model;

 @ExcelColumn(name = "Color")
 public String color;

 @ExcelColumn(name = "Year", numberFormat = "0")
 public Integer year;

 @ExcelColumn(name = "Price", numberFormat = "$#,##0.00")
 public Double price;

 @ExcelColumn(name = "Date", numberFormat = "YYYY-MM-DD")
 public java.util.Date date;

 @ExcelColumn(name = "Available")
 public Boolean available;

 public String toString() {
  String result = ""
   +"Manufacturer=" + this.manufacturer
   +" Model=" + this.model
   +" Color=" + this.color
   +" Year=" + this.year
   +" Price=" + this.price
   +" Date=" + this.date
   +" Available=" + this.available
   +"";
  return result;
 }
}

TestPoiPOJO.java

import PoiPOJO.PoiPOJOUtils;

import org.apache.poi.ss.usermodel.*;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.List;

public class TestPoiPOJO {

 public static void main(String[] args) throws Exception {

  Workbook workbook = WorkbookFactory.create(new FileInputStream("ExcelCars.xlsx"));
  Sheet sheet = workbook.getSheetAt(0);
  List<Car> cars = PoiPOJOUtils.sheetToPOJO(sheet, Car.class);
  System.out.println(cars);

  Car car = new Car();
  car.manufacturer = "Mercedes-Benz";
  car.model = "S 560 4Matic";
  car.color = "Bordeaux";
  car.year = 2019;
  car.price = 78456.78;
  car.date = new java.util.Date();
  car.available = true;

  cars.add(car);

  sheet = workbook.createSheet();
  PoiPOJOUtils.pojoToSheet(sheet, cars);

  FileOutputStream out = new FileOutputStream("ExcelCarsNew.xlsx");
  workbook.write(out);
  out.close();
  workbook.close();
 }
}

必须在第一张表中包含您的ExcelCars.xlsx示例汽车表。列的顺序是灵活的。只有标题必须与ExcelColumnclass 中的注释名称相对应Car

于 2019-11-23T14:10:04.553 回答
3

可能使用oCell库将 Excel 映射到 POJO 和 POJO 到 Excel 会更容易。

https://github.com/rushuat/ocell

<dependency>
  <groupId>io.github.rushuat</groupId>
  <artifactId>ocell</artifactId>
  <version>0.1.3</version>
</dependency>

此外,该库支持几种类型的注释(oCell、Jackson、JPA)和其他映射功能(数据转换、单元格格式、字段忽略)。

汽车 POJO:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Car {

  @FieldName("Manufacturer")
  private String manufacturer;

  @FieldName("Model")
  private String model;

  @FieldName("Color")
  private String color;

  @FieldAlignment(horizontal = "right")
  @FieldConverter(YearConverter.class)
  @FieldName("Year")
  private String year;

  @FieldAlignment(horizontal = "right")
  @FieldFormat("_($* #,##0.00_);_($* (#,##0.00);_($* \"-\"??_);_(@_)")
  @FieldConverter(PriceConverter.class)
  @FieldName("Price")
  private BigDecimal price;
}

读/写 Excel:

Car hondaCar = new Car("Honda", "Pilot", "White", "2019", new BigDecimal(39000));
Car chevyCar = new Car("Chevy", "Silverado", "Green", "2018", new BigDecimal(34000));
Car toyotaCar = new Car("Toyota", "Corolla", "Silver", "2002", new BigDecimal(4000));

try (Document document = new Document()) {
  List<Car> cars = Arrays.asList(hondaCar, chevyCar, toyotaCar);
  document.addSheet(cars);
  document.toFile("cars.xlsx");
}

try (Document document = new Document()) {
  document.fromFile("cars.xlsx");
  List<Car> cars = document.getSheet(Car.class);
}

场转换器:

public class YearConverter implements ValueConverter<String, Number> {

  @Override
  public String convertInput(Number value) {
    return value == null ? null : String.valueOf(value.intValue());
  }

  @Override
  public Number convertOutput(String value) {
    return value == null ? null : Integer.valueOf(value);
  }
}

public class PriceConverter implements ValueConverter<BigDecimal, Number> {

  @Override
  public BigDecimal convertInput(Number value) {
    return value == null ? null : new BigDecimal(value.longValue());
  }

  @Override
  public Number convertOutput(BigDecimal value) {
    return value == null ? null : value.longValue();
  }
}

@FieldFormat 来源:

使用 Apache POI 的基本 Excel 货币格式

于 2021-10-22T22:00:31.073 回答
1

@Axel Ritcher 的回答略有不同,使用并行流和带有 Set Field 的 Java 对象(并且没有公式评估):

public class ExcelFileUtils {


    @SneakyThrows
    // Call this using ExcelFileUtils.sheetToPOJO(new FileInputStream("yourExcl.xlsx"),YourPojo.class)
    public static <T> List<T> sheetToPOJO(InputStream is, Class<T> beanClass) {
        Workbook workbook = WorkbookFactory.create(is);
        Sheet sheet=workbook.getSheetAt(0);
        Map<Integer, String> colHeadersByColIdx = getColHeadersByCoIndex(sheet);
        Map<String, Field> beanFieldsByExlColName=beanFieldsByExlColName(beanClass);
        return IntStream.range(sheet.getFirstRowNum()+1,sheet.getLastRowNum())
                .parallel()
                .mapToObj(rowNum->{
                    T bean = null;
                    try {
                        bean =beanClass.getDeclaredConstructor().newInstance();
                        Row currentRow=sheet.getRow(rowNum);
                        if(Objects.isNull(currentRow)) currentRow=sheet.createRow(rowNum);
                        Row finalCurrentRow = currentRow;
                        T finalBean = bean;
                        colHeadersByColIdx.keySet().parallelStream()
                                .forEach(colIdx->{
                                    String colName=colHeadersByColIdx.get(colIdx);
                                    Cell cell=finalCurrentRow.getCell(colIdx);
                                    if(Objects.isNull(cell))cell=finalCurrentRow.createCell(colIdx);
                                    String cellValue=cell.getStringCellValue();
                                    Field fieldForColName=beanFieldsByExlColName.get(colName);
                                    fieldForColName.setAccessible(true);
                                    try {
                                        if (fieldForColName.getType() == String.class) {
                                            fieldForColName.set(finalBean, cellValue);
                                        }
                                        if(fieldForColName.getType() == Double.class){
                                            fieldForColName.set(finalBean,cell.getNumericCellValue());
                                        }
                                        if(fieldForColName.getType() == Set.class ){
                                            fieldForColName.set(finalBean, Arrays.stream(cellValue.split(",")).collect(Collectors.toSet()));
                                        }
                                    }catch (IllegalAccessException ex){
                                        throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR,ex.getMessage());
                                    }

                                });
                    } catch (InstantiationException | IllegalAccessException | InvocationTargetException |NoSuchMethodException e) {
                        throw new ResponseStatusException(HttpStatus.BAD_REQUEST,e.getMessage());
                    }


                    return bean;
                }).collect(Collectors.toList());


    }

    private static <T> Map<String, Field> beanFieldsByExlColName(Class<T> beanClass){
        Map<String, Field> beanFieldsByExlColName=new HashMap<>();
        Arrays.stream(beanClass.getDeclaredFields())
                .parallel()
                .filter(field -> field.isAnnotationPresent(ExcelColumn.class))
                .forEach(field -> {
                    ExcelColumn ec = field.getAnnotation(ExcelColumn.class);
                    beanFieldsByExlColName.put(ec.name(),field);
                });
        return beanFieldsByExlColName;
    }

    private static  Map<Integer, String> getColHeadersByCoIndex(Sheet sheet){
        Map<Integer, String> colHeadersByColIdx = new HashMap<Integer, String>();
        Row row1 = sheet.getRow(sheet.getFirstRowNum());
        for(Cell cell : row1){
            int colIdx=cell.getColumnIndex();
            colHeadersByColIdx.put(colIdx,cell.getStringCellValue());
        }
        return colHeadersByColIdx;
    }
}


请注意,此示例假定您的 pojo 中有 String、Double 和 Set,并且与 Set 对应的 excel 列具有逗号分隔值。

例如 :

POJO:

@Data
public  class TestProduct{
    @ExcelColumn(name = "Product Name")
    private String productName;
    @ExcelColumn(name = "Image Urls")
    private Set<String> mediaUrls;
}

和 Excel 表: 在此处输入图像描述

于 2020-06-15T22:10:16.200 回答
0

我想找到一种将 xls/xlsx 文件解析为 pojo 列表的简单方法。经过一番搜索,我没有发现任何方便的东西,而是希望快速开发它。现在我可以通过简单地调用来获得 pojos:

InputStream is = this.getClass().getResourceAsStream("/ExcelUtilsTest.xlsx");
List<Pojo> pojos = ExcelToPojoUtils.toPojo(Pojo.class, is);

如果有兴趣看看它:

https://github.com/ZPavel/excelToPojo

于 2020-04-17T11:59:47.207 回答