java POI one-to-many-to-many import excel tool class, 2023 is not easy to use, you hit me

Business scenario: When the user needs to import a one-to-many-to-many excel form, many solutions found on the Internet are not general enough, so I wrote a tool class based on apache POI to deal with this scenario.

Without further ado, let's look at the effect first, if the effect does not meet your expectations, see you soon!

1. Realize the effect:

A brief introduction, a total of four MySQL tables: school, teacherMy, classMy, studentMy
direct relationship:

  1. A "School" may have multiple teachers
  2. A "teacher" may have multiple classes
  3. A "class" may have multiple students

1.1.excel format

Note: The analysis of one-to-many format in this article is calculated according to the "row height of the merged cell", that is to say, the "merged cell" is "one", and the row height of the following cell is "less than" the row of the previous cell Higher is "many"
insert image description here

2. Parsed format

After parsing, screenshot of debug breakpoint
insert image description here
After parsing, screenshot of console printing result:
It can be clearly seen from the figure below that the relationship of "merged cells" in excel is perfectly mapped to our java entity class hierarchical relationship!
insert image description here

2. Design idea:

1. Traverse all the "rows" corresponding to the current sheet page of the current excel written by the user
2. Get the maximum row height of the merged cell of the current "outermost element", which is the cell column corresponding to "School", as this The row height of the entire business data is analyzed for a complete data each time.
3. Use the "custom annotation and recursive method" to analyze the correspondence between cells and our business entity classes, here Correspondence includes: "Between Entity Classes" one-to-many relationship mapping, "cell and entity class field" mapping
4. During this parsing period, it also includes the verification of required cells, etc. (subsequent optimization of multiple Validation, such as text length, time format, etc.)

This article uses apache POI for implementation, and maven relies on the following

备注:因为博主用的是较高版本apache poi的依赖,可能有些方法不兼容低版本。
可以自行百度替换对应的版本写法即可,很简单的!
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>5.2.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.2.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-scratchpad</artifactId>
            <version>3.17</version>
        </dependency>

3. Code display:

3.1. Business entity class

3.1.1 school school entity class

package com.lzq.learn.test.POI一对多数据结构导入.复用.entity;

import com.lzq.learn.test.POI一对多数据结构导入.复用.anno.MyExcel;
import com.lzq.learn.test.POI一对多数据结构导入.复用.anno.MyExcelCollection;
import lombok.Data;

import java.time.LocalDate;
import java.util.List;

/**
 * 学校 实体类
 * @author LiuZhiQiang
 */
@Data
public class School {
    
    

    @MyExcel(name = "学校名称")
    private String schoolName;

    @MyExcel(name = "建校日期")
    private LocalDate schoolCreateDate;

    @MyExcel(name = "学校总人数")
    private Integer schoolPeopleTotal;

    @MyExcelCollection
    private List<TeacherMy> teacherMyList;


    public String printInfo() {
    
    
        String info =  "学校信息 :{" +
                "schoolName='" + schoolName + '\'' +
                ", schoolCreateDate=" + schoolCreateDate +
                ", schoolPeopleTotal=" + schoolPeopleTotal +
                '}';
        System.out.println(info);
        return info;
    }
}


3.1.2 teacherMy teacher entity class

package com.lzq.learn.test.POI一对多数据结构导入.复用.entity;

import com.lzq.learn.test.POI一对多数据结构导入.复用.anno.MyExcel;
import com.lzq.learn.test.POI一对多数据结构导入.复用.anno.MyExcelCollection;
import lombok.Data;

import java.util.List;

/**
 * 老师 实体类
 * @author LiuZhiQiang
 */
@Data
public class TeacherMy {
    
    

    @MyExcel(name = "老师姓名")
    private String teacherName;

    @MyExcel(name = "老师编号")
    private String teacherCode;

    @MyExcel(name = "老师年龄")
    private Integer teacherAge;

    @MyExcel(name = "老师性别")
    private String teacherSex;

    @MyExcelCollection
    private List<ClassMy> classMyList;


    public String printInfo() {
    
    
        String info = "     老师信息 {" +
                "teacherName='" + teacherName + '\'' +
                ", teacherCode='" + teacherCode + '\'' +
                ", teacherAge=" + teacherAge +
                ", teacherSex='" + teacherSex + '\'' +
                '}';
        System.out.println(info);
        return info;
    }

}


3.1.3 classMy class entity class

package com.lzq.learn.test.POI一对多数据结构导入.复用.entity;

import cn.afterturn.easypoi.excel.annotation.Excel;
import com.lzq.learn.test.POI一对多数据结构导入.复用.anno.MyExcel;
import com.lzq.learn.test.POI一对多数据结构导入.复用.anno.MyExcelCollection;
import lombok.Data;

import java.util.List;

/**
 * 班级 实体类
 * @author LiuZhiQiang
 */
@Data
public class ClassMy {
    
    

    @MyExcel(name = "班级名称")
    private String className;

    @MyExcel(name = "班级编号")
    private String classCode;

    @MyExcelCollection
    private List<StudentMy> studentMyList;


    public String printInfo() {
    
    
        String info =  "        班级信息 {" +
                "className='" + className + '\'' +
                ", classCode='" + classCode + '\'' +
                '}';
        System.out.println(info);
        return info;
    }
}


3.1.4 studetMy student entity class

package com.lzq.learn.test.POI一对多数据结构导入.复用.entity;

import com.lzq.learn.test.POI一对多数据结构导入.复用.anno.MyExcel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

/**
 * 学生实体类
 * @author LiuZhiQiang
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class StudentMy {
    
    

    @MyExcel(name = "学生姓名")
    private String studentName;

    @MyExcel(name = "身份证号码")
    private String idCard;

    @MyExcel(name = "入学成绩", required = true)
    private BigDecimal admissionScore;



    public String printInfo() {
    
    
        String info =  "            学生信息 {" +
                "studentName='" + studentName + '\'' +
                ", idCard='" + idCard + '\'' +
                ", admissionScore=" + admissionScore +
                '}';
        System.out.println(info);
        return info;
    }
}


3.2 Custom annotations

3.2.1 Notes on the mapping relationship between entity classes and cells

package com.lzq.learn.test.POI一对多数据结构导入.复用.anno;

import java.lang.annotation.*;

/**
 * @author x
 */
@Documented
@Target(value = ElementType.FIELD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface MyExcel {
    
    


    /**
     * Excel表格字段名称
     */
    String name() default "";

    /**
     * Excel表格字段是否必填
     */
    boolean required() default false;

    /**
     * Excel表格字段宽度
     */
    short width() default 20;

    /**
     * Excel表格字段排序
     */
    int sort() default 0;
}


3.2.2 Entity class and entity class relationship mapping annotation

package com.lzq.learn.test.POI一对多数据结构导入.复用.anno;

import java.lang.annotation.*;

/**
 * @author LiuZhiQiang
 */
@Documented
@Target(value = ElementType.FIELD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface MyExcelCollection {
    
    
}


3.3 dto intermediate operation class

3.3.1 The class that needs to be imported ImportClass

package com.lzq.learn.test.POI一对多数据结构导入.复用.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/**
 * 表头对应的业务实体类结构
 * @author x
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ImportClass {
    
    

    /**             主类              **/
    private Class mainClass;

    /**             作为其他类的从类列表字段名称          **/
    private String fieldName;

    private List<ImportField> MainClassImportFieldList;

    /**             对应的从类           **/
    private List<ImportClass> slaveClassList;

}


3.3.2 Fields that need to be imported ImportField

package com.lzq.learn.test.POI一对多数据结构导入.复用.dto;

import com.lzq.learn.test.POI一对多数据结构导入.复用.entity.School;
import com.lzq.learn.test.POI一对多数据结构导入.复用.anno.MyExcel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList;

/**
 * @author x
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ImportField {
    
    

    private Integer columnIndex;
    private Boolean required;
    private String fieldName;
    //单元格表头名称
    private String cellHeadName;
    private Class<?> fieldType;
    private Field field;


    public static ImportField buildByField(Field field, int index) {
    
    
        ImportField importField = new ImportField();
        importField.setColumnIndex(index);
        importField.setRequired(field.getAnnotation(MyExcel.class).required());
        importField.setCellHeadName(field.getAnnotation(MyExcel.class).name());
        importField.setFieldName(field.getName());
        importField.setFieldType(field.getType());
        importField.setField(field);
        return importField;
    }

}


3.4 Import tool class (core)

3.4.1 Tool class ImportUtils

package com.lzq.learn.test.POI一对多数据结构导入.复用.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.lzq.learn.test.POI一对多数据结构导入.复用.anno.MyExcel;
import com.lzq.learn.test.POI一对多数据结构导入.复用.anno.MyExcelCollection;
import com.lzq.learn.test.POI一对多数据结构导入.复用.dto.ImportClass;
import com.lzq.learn.test.POI一对多数据结构导入.复用.dto.ImportField;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;

import java.lang.reflect.*;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author LiuZhiQiang
 */
public class ImportUtils {
    
    

    public static void main(String[] args) throws Exception {
    
    
//        parse(0, 1, School.class);
    }

    private static List<String> importHeader(Sheet sheet, int headerRowIndex) {
    
    
        Row row = sheet.getRow(headerRowIndex);
        List<String> headNameList = new ArrayList<>();
        for (int i = 0; i < row.getPhysicalNumberOfCells(); i++) {
    
    
            Cell cell = row.getCell(i);
            String headCellValue = cell.getStringCellValue();
            headNameList.add(headCellValue);

        }
        return headNameList;
    }


    // 反射根据 class 对象获取对应的字段
    private static List<Field> getExportFields(Class clazz) {
    
    
        List<Field> collect = Arrays.stream(clazz.getDeclaredFields()).filter(
                field -> field.isAnnotationPresent(MyExcel.class)
        ).sorted(
                Comparator.comparing(field -> field.getAnnotation(MyExcel.class).sort())
        ).collect(Collectors.toList());

        return collect;
    }

    // 获取 ImportClass 对应的《一对多,一对多 多层》的 数据结构
    public static ImportClass getImportClass(Class clazz, ImportClass importClass, List<String> headNameList) {
    
    
        if (importClass == null) {
    
    
            importClass = new ImportClass();
        }
        importClass.setMainClass(clazz);
        ArrayList<ImportField> mainClassImportFieldList = new ArrayList<>();
        for (Field declaredField : clazz.getDeclaredFields()) {
    
    
            declaredField.setAccessible(true);
            if (declaredField.isAnnotationPresent(MyExcel.class)) {
    
    
//                mainClassImportFieldList.add(ImportField.buildByField(declaredField, declaredField.getAnnotation(MyExcel.class).sort()));
                String MyExcelAttributeName = declaredField.getAnnotation(MyExcel.class).name();
                if (headNameList.contains(MyExcelAttributeName)) {
    
    
                    mainClassImportFieldList.add(ImportField.buildByField(declaredField, headNameList.indexOf(MyExcelAttributeName)));
                }
            }
            boolean annotationPresent = declaredField.isAnnotationPresent(MyExcelCollection.class);
            if (annotationPresent) {
    
    
                Class<?> classByListField = ImportUtils.getClassByListField(declaredField);
                if (classByListField == null) {
    
    
                    continue;
                }
                List<ImportClass> slaveClassListOld = importClass.getSlaveClassList();
                if (CollUtil.isEmpty(slaveClassListOld)) {
    
    
                    slaveClassListOld = new ArrayList<>();
                }
                ImportClass slaveClass = new ImportClass();
                slaveClass.setFieldName(declaredField.getName());
                ImportClass classByListFieldImportClass = ImportUtils.getImportClass(classByListField, slaveClass, headNameList);
                slaveClassListOld.add(classByListFieldImportClass);
                importClass.setSlaveClassList(slaveClassListOld);
            }
        }
        importClass.setMainClassImportFieldList(mainClassImportFieldList);
        return importClass;
    }

    // 获取 List<T>字段 对应的泛型类型
    public static Class<?> getClassByListField(Field field) {
    
    
        field.setAccessible(true);
        if (field.getType() == java.util.List.class) {
    
    
            // 如果是List类型,得到其Generic的类型
            Type genericType = field.getGenericType();
            if (genericType == null) {
    
    
                return null;
            }
            // 如果是泛型参数的类型
            if (genericType instanceof ParameterizedType) {
    
    
                ParameterizedType pt = (ParameterizedType) genericType;
                //得到泛型里的class类型对象
                return (Class<?>) pt.getActualTypeArguments()[0];
            }
        }
        return null;
    }


    public static <T> List<T> buildDataList(Sheet sheet, Class<T> tClass, int headRowIndex, int dataRowIndex) {
    
    
        // 获取 tClass 对应的所有字段, 然后寻找是否有字段带有注解@MyExcelCollection
        ArrayList<T> dataList = new ArrayList<>();
        // 1.获取表头信息对应的单元格列名
//        List<Map<String, Object>> headerFieldList = ImportUtils.importHeader(tClass, sheet, headRowIndex);
        List result = new ArrayList();
        for (int i = dataRowIndex; i < sheet.getPhysicalNumberOfRows(); ) {
    
    
            Row row = sheet.getRow(i);
            //当前这行全部为空或者空格就忽视当前这行
            if (isAllRowEmpty(row, sheet.getRow(headRowIndex))) {
    
    
                continue;
            }
            T tInstance = ImportUtils.parseRowData(tClass, sheet, i);
            int rowHeight = ImportUtils.getRowSpan(sheet, i, 0);
            i = i + rowHeight;
            dataList.add(tInstance);
        }
        return dataList;
    }


    /**
     * 解析一条完整的数据
     **/
    public static <T> T parseRowData(Class<T> tClass, Sheet sheet, int rowIndex) {
    
    
        try {
    
    
            Row row = sheet.getRow(rowIndex);
            // 假设当前是
            T instance = null;
            // TODO: 2023/8/4  先写死表头index=0
            List<String> headNameList = ImportUtils.importHeader(sheet, 0);
            ImportClass importClass = ImportUtils.getImportClass(tClass, null, headNameList);

            instance = tClass.newInstance();
            Set<Integer> mainClassRowHeightSet = new LinkedHashSet<>();
            for (ImportField importField : importClass.getMainClassImportFieldList()) {
    
    
                int slaveRowIndex = rowIndex;
                Integer columnIndex = importField.getColumnIndex();
                int mainClassRowHeight = ImportUtils.getRowSpan(sheet, rowIndex, columnIndex);
                Cell cell = row.getCell(columnIndex);
                mainClassRowHeightSet.add(mainClassRowHeight);
                Method method = null;

                method = tClass.getMethod(getMethodName("set", importField.getFieldName()), importField.getFieldType());

                if (method == null) {
    
    
                    // 说明当前对象没有这个字段
                }
                if (method != null) {
    
    
                    if (importField.getRequired() && cell == null) {
    
    
                        StringBuilder builder = new StringBuilder()
                                .append("第" )
                                .append(rowIndex + 1)
                                .append("行,第" )
                                .append(importField.getColumnIndex() + 1)
                                .append("列,未填写" );
                        throw new RuntimeException(builder.toString());
                    }
                    if (Objects.nonNull(method) && Objects.nonNull(cell)) {
    
    
                        Object cellValue = getCellValue(cell, importField.getFieldType());
                        if (Objects.nonNull(cellValue)) {
    
    
                            method.invoke(instance, cellValue);
                        }
                    }
                }

            }
            // 合并单元格后的当前行拥有的的行高
            List<Integer> heightList = mainClassRowHeightSet.stream().sorted().collect(Collectors.toList());
            Integer maxHeight = heightList.get(0);
            // 遍历每个子元素, 进行单元格内的数据填充
            for (ImportClass slaveImportClass : importClass.getSlaveClassList()) {
    
    
                // 每个子元素都从 父元素的《slaveRowIndex》开始
                // 遍历父元素拥有的行内,对应的有多少个子元素
                List parseRowItemList = ImportUtils.parseRowItemList(sheet, rowIndex, maxHeight, slaveImportClass);
                // 给list字段赋值: 写法一暂时行不通
//                Method slaveListMethod = null;
//                slaveListMethod = slaveImportClass.getMainClass().getMethod(getMethodName("set", slaveImportClass.getFieldName()), slaveImportClass.getMainClass());
//                if (Objects.nonNull(slaveListMethod)) {
    
    
//                    slaveListMethod.invoke(instance, parseRowItemList);
//                }
                // 给list字段赋值:写法二
                Field declaredListField = instance.getClass().getDeclaredField(slaveImportClass.getFieldName());
                declaredListField.setAccessible(true);
                declaredListField.set(instance, parseRowItemList);
            }
            return instance;
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取父元素行内有多少个子元素,返回值是一个List
     **/
    public static <T> List<T> parseRowItemList(Sheet sheet, int parentRowIndex, int parentMaxHeight, ImportClass slaveImportClass) throws Exception {
    
    
        Class<T> slaveClass = slaveImportClass.getMainClass();
        List<ImportField> slaveImportFieldList = slaveImportClass.getMainClassImportFieldList();
        ArrayList<T> slaveDataList = new ArrayList<>();
        // 每个子元素都从 父元素的《第一行》开始
        // 遍历父元素拥有的行内,对应的有多少个子元素
        for (int slaveRowIndex = parentRowIndex; slaveRowIndex < parentRowIndex + parentMaxHeight; ) {
    
    
            T slaveInstance = slaveClass.newInstance();
            int slaveClassRowHeight = 1;
            for (ImportField slaveImportField : slaveImportFieldList) {
    
    
                Row slaveRow = sheet.getRow(slaveRowIndex);
                Integer slaveColumnIndex = slaveImportField.getColumnIndex();
                // 当前单元格 对应的行高
                slaveClassRowHeight = ImportUtils.getRowSpan(sheet, slaveRowIndex, slaveColumnIndex);
                Cell slaveRowCell = slaveRow.getCell(slaveColumnIndex);
//                        slaveClassRowHeightSet.add(slaveClassRowHeight);
                Method slaveMethod = null;
                slaveMethod = slaveClass.getMethod(getMethodName("set", slaveImportField.getFieldName()), slaveImportField.getFieldType());
                if (slaveMethod == null) {
    
    
                    // 说明当前对象没有这个字段
                    System.out.println("说明当前对象没有这个字段" );
                }
                if (slaveMethod != null) {
    
    
                    if (slaveImportField.getRequired() && slaveRowCell == null) {
    
    
                        StringBuilder builder = new StringBuilder()
                                .append("第" )
                                .append(slaveRowIndex + 1)
                                .append("行,第" )
                                .append(slaveImportField.getColumnIndex() + 1)
                                .append("列,未填写" );
                        throw new RuntimeException(builder.toString());
                    }
                    if (Objects.nonNull(slaveMethod) && Objects.nonNull(slaveRowCell)) {
    
    
                        Object cellValue = getCellValue(slaveRowCell, slaveImportField.getFieldType());
                        if (Objects.nonNull(cellValue)) {
    
    
                            slaveMethod.invoke(slaveInstance, cellValue);
                        }
                    }
                }
            }
            // 子元素的子元素进行填充值, 此处使用了递归
            if (CollUtil.isNotEmpty(slaveImportClass.getSlaveClassList())) {
    
    
                for (ImportClass slaveClassSlaveClass : slaveImportClass.getSlaveClassList()) {
    
    
                    List<Object> slaveListSlaveList = ImportUtils.parseRowItemList(sheet, slaveRowIndex, slaveClassRowHeight, slaveClassSlaveClass);
                    Method slaveListSlaveListMethod = null;
                    slaveListSlaveListMethod = slaveImportClass.getMainClass().getMethod(getMethodName("set", slaveClassSlaveClass.getFieldName()), java.util.List.class);
                    if (Objects.nonNull(slaveListSlaveListMethod)) {
    
    
                        slaveListSlaveListMethod.invoke(slaveInstance, slaveListSlaveList);
                    } else {
    
    
                        System.out.println("说明当前对象没有这个List字段" );
                    }
                }
            }
            slaveRowIndex = slaveRowIndex + slaveClassRowHeight;
            slaveDataList.add(slaveInstance);
        }
        return slaveDataList;
    }


    private static Object getCellValue(Cell cell, Class fieldType) {
    
    
        Object result = null;
        CellType cellType = cell.getCellType();
        switch (cellType) {
    
    
            case _NONE:
                break;
            case BLANK:
                break;
            case STRING:
                result = cell.getStringCellValue().trim();
                break;
            case NUMERIC:
                double numericCellValue = cell.getNumericCellValue();
                if (fieldType.equals(LocalDate.class)) {
    
    
                    result = cell.getDateCellValue().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
                } else if (fieldType.equals(LocalDateTime.class)) {
    
    
                    result = cell.getDateCellValue().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
                    //TODO 查看此处为何会报错,是版本的原因吗?
                    result = cell.getLocalDateTimeCellValue();
                } else if (fieldType.equals(String.class)) {
    
    
                    DecimalFormat decimalFormat = new DecimalFormat("#" );
                    result = decimalFormat.format(cell.getNumericCellValue());
                } else if (fieldType == Short.class) {
    
    
                    result = (short) numericCellValue;
                } else if (fieldType == Integer.class) {
    
    
                    result = (int) cell.getNumericCellValue();
                } else if (fieldType == Long.class) {
    
    
                    result = (long) numericCellValue;
                } else if (fieldType == Float.class) {
    
    
                    result = (float) numericCellValue;
                } else if (fieldType == Double.class) {
    
    
                    result = numericCellValue;
                } else if (fieldType == BigDecimal.class) {
    
    
                    result = BigDecimal.valueOf(cell.getNumericCellValue());
                } else {
    
    
                    result = cell.getNumericCellValue();
                }
                break;
            case FORMULA:
                break;
            case BOOLEAN:
                break;
            case ERROR:
                break;
        }
        return result;
    }

    private static String getMethodName(String prefix, String fieldName) {
    
    
        StringBuilder builder = new StringBuilder()
                .append(prefix)
                .append(fieldName.substring(0, 1).toUpperCase())
                .append(fieldName.substring(1));
        return builder.toString();
    }

    /**
     * 验证excel是否全部为空
     *
     * @param row      当前行
     * @param firstRow 第一行标题行
     * @return
     */
    public static boolean isAllRowEmpty(Row row, Row firstRow) {
    
    
        if (row == null) {
    
    
            return true;
        }
        int count = 0;
        //单元格数量
        int rowCount = firstRow.getLastCellNum() - firstRow.getFirstCellNum();
        //判断多少个单元格为空
        for (int c = 0; c < rowCount; c++) {
    
    
            Cell cell = row.getCell(c);
            if (cell == null || cell.getCellType() == CellType.BLANK || StrUtil.isEmpty((cell + "" ).trim())) {
    
    
                count += 1;
            }
        }
        if (count == rowCount) {
    
    
            return true;
        }
        return false;
    }

    // 查看某个单元格是否在 合并区域内, 返回值是 该合并区域内的高度差
    private static int getRowSpan(Sheet sheet, int rowIndex, int columnIndex) {
    
    
        List<CellRangeAddress> mergedRegions = sheet.getMergedRegions();
        for (CellRangeAddress mergedRegion : mergedRegions) {
    
    
            if (mergedRegion.isInRange(rowIndex, columnIndex)) {
    
    
                int count = mergedRegion.getLastRow() - mergedRegion.getFirstRow() + 1;
                if (count < 1) {
    
    
                    throw new RuntimeException("计算单元格高度异常,程序终止" );
                }
                return count;
            }
        }
        return 1;
    }


}

3.5 Use a use case to show ImportUtilsTestMain

package com.lzq.learn.test.POI一对多数据结构导入.复用;

import com.lzq.learn.test.POI一对多数据结构导入.复用.entity.ClassMy;
import com.lzq.learn.test.POI一对多数据结构导入.复用.entity.School;
import com.lzq.learn.test.POI一对多数据结构导入.复用.entity.StudentMy;
import com.lzq.learn.test.POI一对多数据结构导入.复用.entity.TeacherMy;
import com.lzq.learn.test.POI一对多数据结构导入.复用.util.ImportUtils;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;

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

/**
 * @author x
 */
public class ImportUtilsTestMain {
    
    


    public static void main(String[] args) throws Exception{
    
    
//        testSheet0();
        testSheet1();
    }

    /**                         测试sheet0                            **/
    public static void testSheet0() throws Exception{
    
    
        // 打开 Excel 文件
        String workingDir = System.getProperty("user.dir" );
        String filePath = workingDir + "//src//main//resources//importExcel//fuyong//ontToMoreFuYong.xlsx";
        FileInputStream fis = new FileInputStream(filePath);
        Workbook wb = WorkbookFactory.create(fis);
        int sheetIndex = 0;
        int headRowSpan = 0;
        int dataRowStartIndex = 1;
        // 解析表格数据
        Sheet sheet = wb.getSheetAt(sheetIndex);
        // todo 递归结构待优化
        List<School> schoolList = ImportUtils.buildDataList(sheet, School.class, headRowSpan, dataRowStartIndex);
        schoolList.forEach(System.out::println);
    }

    /**                             测试 sheet1                       **/
    public static void testSheet1() throws Exception{
    
    
        // 打开 Excel 文件
        String workingDir = System.getProperty("user.dir" );
        String filePath = workingDir + "//src//main//resources//importExcel//fuyong//ontToMoreFuYong.xlsx";
        FileInputStream fis = new FileInputStream(filePath);
        Workbook wb = WorkbookFactory.create(fis);
        int sheetIndex = 1;
        int headRowSpan = 0;
        int dataRowStartIndex = 1;
        // 解析表格数据
        Sheet sheet = wb.getSheetAt(sheetIndex);
        // todo 递归结构待优化
        List<School> schoolList = ImportUtils.buildDataList(sheet, School.class, headRowSpan, dataRowStartIndex);
//        schoolList.forEach(System.out::println);
        System.out.println();
        System.out.println("解析结果如下:");
        for (School school : schoolList) {
    
    
            school.printInfo();
            for (TeacherMy teacherMy : school.getTeacherMyList()) {
    
    
                teacherMy.printInfo();
                for (ClassMy classMy : teacherMy.getClassMyList()) {
    
    
                    classMy.printInfo();
                    for (StudentMy studentMy : classMy.getStudentMyList()) {
    
    
                        studentMy.printInfo();
                    }
                }
            }
            System.out.println();
        }
    }
}


Summarize

In general, the implementation idea is: reflection + custom annotation + recursion.
Generally speaking, as long as Excel is created according to the rule of "merging cells represent corresponding entity class relationships", the relationship of merged cells in excel can be converted into the hierarchical relationship of java entity classes.

If there are deficiencies in the article, or where there are mistakes, you can comment in the comment area below if you have any questions. Thank you for your message. If you find it useful, remember to bookmark + like it.

Guess you like

Origin blog.csdn.net/lzq2357639195/article/details/132123018