“POI+注解+反射”实现Excel单sheet页导入单个Bean中;支持指定行范围导入到Bean的List<SubBean>中

因项目需求,Excel模板存在多处行合并和列合并,需要导入指定单元格的数据,并且存在多行数据相同情况;同时不知道后续Excel模板是否会变动。所以利用POI、注解和反射实现一个工具类,需要维护与单sheet页对应的Java Bean上的注解。

一、注解:

1、指定单元格注解,指定行、列、数据类型

/**
 * Excel单元格
 */
@Documented
@Inherited
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelCell {
    /**
     * excel行数
     *
     * @return 行数
     */
    int row() default 0;

    /**
     * excel列数
     *
     * @return 列数
     */
    int col() default 0;

    /**
     * excel单元格对应java的数据类型
     *
     * @return 数据类型
     */
    String type() default "String";
}

2、指定开始行和结束行

@Documented
@Inherited
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelRow {

    /**
     * excel开始行
     *
     * @return 开始行
     */
    int startRow();

    /**
     * excel结束行
     *
     * @return 结束行
     */
    int endRow();

}

二、工具类

大概思路:

        利用反射获取Model的属性,根据属性上的注解(ExcelCell)得到该属性对应sheet页哪个单元格和数据类型,然后获取单元格的数据并放到Model中;如果单元格中存在某几行的数据可以抽取成一个SubModel的列表,此时根据注解(ExcelRow)得到行范围,根据SubModel和行范围获取到一个数据集并放到Model中。

public class ExcelImportUtil {

    /**
     * 根据单元格的row和col导入一个sheet页的数据
     * <p>
     * 一个sheet页对应一个Java bean
     * 每个属性上添加@ExcelCell注解,指定col、row和数据类型
     * <p>
     * 如果当前sheet页有多行相同数据,
     * 可以在Java bean中添加相对应的List并添加注解@ExcelRow指定开始行和结束行,List中的bean每个属性上需要添加注解@ExcelCell指定所属列
     *
     * @param clazz    一个sheet页的数据类型
     * @param subClazz sheet页中多行重复数据类型
     * @param sheet    一sheet页
     * @return 一个sheet页的数据
     * @throws Exception
     */
    public static Object getSheetData(Class<?> clazz, Class<?> subClazz, XSSFSheet sheet) throws Exception {
        Object model = clazz.newInstance();
        // 获取所有属性
        Field[] fields = model.getClass().getDeclaredFields();
        for (Field field : fields) {
            // 获取属性注解
            ExcelCell excelCell = field.getAnnotation(ExcelCell.class);
            if (excelCell != null) {
                // 根据注解的行和列获取指定单元格
                XSSFCell cell = sheet.getRow(excelCell.row()).getCell(excelCell.col());
                if (cell != null) {
                    getCellData(excelCell, field, cell, model);
                }
            } else if (subClazz != null) {
                // sheet页中存在多行数据可以存放到同一类型的subBean中的情况
                ExcelRow excelRow = field.getAnnotation(ExcelRow.class);
                if (excelRow != null) {
                    List<Object> subModels = new ArrayList<>();
                    // 根据属性注解上的行范围获取数据并存放到bean中
                    for (int i = excelRow.startRow(); i <= excelRow.endRow(); i++) {
                        Object subModel = subClazz.newInstance();
                        Field[] subFields = subModel.getClass().getDeclaredFields();
                        for (Field subField : subFields) {
                            ExcelCell subCell = subField.getAnnotation(ExcelCell.class);
                            XSSFCell cell = sheet.getRow(i).getCell(subCell.col());
                            if (cell != null) {
                                getCellData(subCell, subField, cell, subModel);
                            }
                        }
                        subModels.add(subModel);
                    }
                    field.setAccessible(true);
                    field.set(model, subModels);
                }
            }
        }
        return model;
    }

    /**
     * 获取单元格中的数据并存放到model中
     * @param excelCell 单元格注解
     * @param field 属性
     * @param cell  单元格
     * @param model bean
     * @throws Exception
     */
    private static void getCellData(ExcelCell excelCell, Field field, XSSFCell cell, Object model) throws Exception {
        switch (excelCell.type()) {
            case "String":
                String stringCellValue = cell.getStringCellValue();
                field.setAccessible(true);
                field.set(model, stringCellValue);
                break;
            case "Integer":
                double age = cell.getNumericCellValue();
                field.setAccessible(true);
                field.set(model, (int) age);
                break;
            case "Double":
                double salary = cell.getNumericCellValue();
                field.setAccessible(true);
                field.set(model, salary);
                break;
            case "Date":
                Date birthday = cell.getDateCellValue();
                field.setAccessible(true);
                field.set(model, birthday);
        }
    }
}

三、使用示例:

1、Excel模板

 

第一行数据与Model中的属性对应

二至四行数据与SubModel中的属性对应

2、声明一个sheet对应的bean

默认行为0、列为0、数据类型为String

@Getter
@Setter
@ToString
public class Model {
    @ExcelCell()
    private String name;
    
    @ExcelCell(col = 1,type = "Integer")
    private Integer age;
    
    @ExcelCell(col = 2,type = "Double")
    private Double salary;
    
    @ExcelCell(col = 3,type = "Date")
    private Date birthday;

    @ExcelRow(startRow = 1,endRow = 2)
    private List<SubModel> subModels;
}

3、声明sheet页中多行相似subBean

不需要声明行,只需要声明列和数据类型

@Getter
@Setter
@ToString
public class SubModel {
    @ExcelCell()
    private String name;
    
    @ExcelCell(col = 1,type = "Integer")
    private Integer age;
    
    @ExcelCell(col = 2,type = "Double")
    private Double salary;
    
    @ExcelCell(col = 3,type = "Date")
    private Date birthday;
}

4、工具类方法调用

public static void main(String[] args) throws Exception {
        File file = new File("C:\\Users\\swt\\Desktop\\测试.xlsx");
        XSSFWorkbook workbook = new XSSFWorkbook(new FileInputStream(file));
        XSSFSheet sheet = workbook.getSheetAt(0);
        Model model = (Model) ExcelImportUtil.getSheetData(Model.class, SubModel.class, sheet);
        System.out.println(model.getName());
        System.out.println(model.getAge());
        System.out.println(model.getSalary());
        System.out.println(model.getBirthday());
        model.getSubModels().forEach(System.out::println);
    }

5、输出:

张三
18
32.25
Sun Sep 12 00:00:00 CST 2021
SubModel(name=李四, age=19, salary=32.25, birthday=Mon Jul 12 00:00:00 CST 2021)
SubModel(name=王五, age=20, salary=32.75, birthday=Wed Sep 15 00:00:00 CST 2021)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值