使用poi解析excel 返回对象

使用poi加自定义注解使用反射机制实现把excel中的row转换成entity。
支持外键查询找
本人公司内部使用jpa框架,所以代码中从SpringContextHolder对象中获取的注解标识的JpaRepository对象,其他持久层框架也可以使用,需要修改查询接口。
这部分为解析excel 对单元格数据格式进行判断


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import xxx.ExcelCheck;
import xxx.BaseEntityStatus;
import xxx.FieldReflectUtil;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * @author yaku
 */
public class ExcelUtil {

    private final static String XLS = ".xls";
    private final static String XLSX = ".xlsx";

    /**
     * 读入excel文件,解析内容后返回结果
     *
     * @param file    excel文件
     * @param handler 解析excel的
     */
    public static ParseExcelResult readExcel(MultipartFile file, Class<?> clazz, ExcelRowsHandler<Object> handler) {
        ParseExcelResult parseExcelResult = new ParseExcelResult();
        parseExcelResult.setErrorRowList(new ArrayList<>());
        // 获得Workbook工作薄对象
        Workbook workbook;
        try {
            workbook = getWorkBook(Objects.requireNonNull(file.getOriginalFilename()), Objects.requireNonNull(file.getInputStream()));
            // 创建返回对象,把每行中的值作为一个数组,所有行作为一个集合返回
            if (workbook.getNumberOfSheets() < 1 || workbook.getSheetAt(0).getLastRowNum() == 0) {
                throw new RuntimeException("无法解析excel数据!");
            }
        } catch (IOException e) {
            throw new RuntimeException("无法解析该Excel");
        }
        // 获得当前sheet工作表
        Sheet sheet = workbook.getSheetAt(0);
        // 获得当前sheet的开始行
        int firstRowNum = sheet.getFirstRowNum();
        // 获取头行数据
        Row headRow = sheet.getRow(0);
        // 获得当前sheet的结束行
        int lastRowNum = sheet.getLastRowNum();
        // 循环除了第一行的所有行
        for (int rowNum = firstRowNum + 1; rowNum < lastRowNum; rowNum++) {
            // 获得当前行
            Row row = sheet.getRow(rowNum);
            ErrorRow errorRow;
            Object o;
            try {
                errorRow = check(headRow, row, clazz);
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                throw new RuntimeException("无法解析该Excel");
            }
            if (null != errorRow) {
                parseExcelResult.getErrorRowList().add(errorRow);
                parseExcelResult.setProcessedRowNum(parseExcelResult.getProcessedRowNum() + 1);
                continue;
            } else {
                o = createEntity(headRow, row, clazz);
            }
            // 改行执行sql 发生错误记录改行
            ErrorRow exceptionRow = handler.execute(rowNum, o);
            if (exceptionRow != null) {
                parseExcelResult.getErrorRowList().add(exceptionRow);
                parseExcelResult.setProcessedRowNum(parseExcelResult.getProcessedRowNum() + 1);
            }
        }
        return parseExcelResult;
    }

    /**
     * 根据当前行数据创建对象
     *
     * @param clazz 对象字节码文件
     * @return 检测通过返回null 检测错误返回ErrorRow对象
     * @throws ClassNotFoundException 找不到class字节码文件
     * @throws InstantiationException 无法创建对象 没有默认构造
     * @throws IllegalAccessException 该对象没有权限访问
     */
    private static ErrorRow check(Row headRow, Row currentRow, Class<?> clazz) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Field[] fields = clazz.getDeclaredFields();
        boolean[] flag = new boolean[headRow.getLastCellNum()];
        for (Field field : fields) {
            ExcelCheck annotation = field.getAnnotation(ExcelCheck.class);
            for (int i = 0; i < currentRow.getLastCellNum(); i++) {
                if (flag[i]) {
                    continue;
                }
                if (annotation.cellName().equals(headRow.getCell(i).getStringCellValue())) {
                    flag[i] = true;
                    // 判断为空单元格不能为空
                    if (annotation.nullAble()) {
                        continue;
                    }
                    if (currentRow.getCell(i).getCellTypeEnum().equals(CellType.BLANK)) {
                        return new ErrorRow(i, currentRow.getRowNum(), annotation.cellName() + "不能为空!");
                    }
                    // 单元格类型校验
                    if (!typeCheck(annotation.type(), currentRow.getCell(i))) {
                        return new ErrorRow(i, currentRow.getRowNum(), annotation.cellName() + "数据格式错误!");
                    }
                }
            }
        }
        return null;
    }

    /**
     * excel每行数据校验
     *
     * @param type entity对象表明的类型
     * @param cell 单元格对象
     * @return 正确 true 错误 false
     */
    private static boolean typeCheck(ExcelCheck.Type type, Cell cell) {
        switch (type) {
            case NUMBER: {
                if (cell.getCellTypeEnum().equals(CellType.NUMERIC)) {
                    try {
                        MathUtils.objectConvertBigDecimal(cell.getStringCellValue());
                    } catch (Exception e) {
                        return false;
                    }
                    return true;
                }
                return false;
            }
            case STRING: {
                return cell.getCellTypeEnum().equals(CellType.STRING);
            }
            case DATETIME: {
                return cell.getCellTypeEnum().equals(CellType.NUMERIC);
            }
            case LINK: {
                if (cell.getCellTypeEnum().equals(CellType.STRING)) {
                    return StringUtils.isNotBlank(cell.getStringCellValue());
                }
                return false;
            }
            default:
                return false;
        }
    }

    /**
     * 根据注解 对象信息 来转换成对应值
     *
     * @param excelCheck 注解对象
     * @param cell       单元格对象
     * @return 对应类型
     */
    private static Object valueFormat(ExcelCheck excelCheck, Cell cell) {
        switch (excelCheck.type()) {
            case NUMBER: {
                return MathUtils.objectConvertBigDecimal(cell.getNumericCellValue());
            }
            case STRING: {
                return cell.getStringCellValue();
            }
            case DATETIME: {
                return new Timestamp(cell.getDateCellValue().getTime());
            }
            case LINK: {
                return linkQuery(excelCheck.repositoryClass(), excelCheck.methodName(), cell.getStringCellValue());
            }
            default:
                return null;
        }
    }

    private static String linkQuery(Class<? extends JpaRepository> clazz, String methodName, String value) {
        try {
            JpaRepository jpaRepository = SpringContextHolder.getBean(clazz);
            Method method = jpaRepository.getClass().getDeclaredMethod(methodName, String.class, Integer.class);
            Object invoke = method.invoke(jpaRepository, value, BaseEntityStatus.ACTIVE);
            if (null == invoke) {
                return null;
            }
            return (String) FieldReflectUtil.getFieldValue(invoke, "id");
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | NoSuchFieldException e) {
            throw new RuntimeException("无法处理该外键值");
        }
    }

    private static Object createEntity(Row headRow, Row currentRow, Class<?> clazz) {
        try {
            boolean[] flag = new boolean[headRow.getLastCellNum()];
            Object entity = clazz.newInstance();
            Field[] fields = entity.getClass().getDeclaredFields();
            for (int i = 0; i < headRow.getLastCellNum(); i++) {
                for (Field field : fields) {
                    if (flag[i]) {
                        continue;
                    }
                    if (headRow.getCell(i).getStringCellValue().equals(field.getAnnotation(ExcelCheck.class).cellName())) {
                        flag[i] = true;
                        Cell cell = currentRow.getCell(i);
                        //如果当前单元格为空时,跳过该单元格
                        if (null == cell || cell.getCellTypeEnum().equals(CellType.BLANK)) {
                            continue;
                        }
                        if (!currentRow.getCell(i).getCellTypeEnum().equals(CellType.BLANK)) {
                            FieldReflectUtil.setFieldValue(entity, field.getName(), valueFormat(field.getAnnotation(ExcelCheck.class), cell));
                        }
                    }
                }
            }
            return entity;
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | NoSuchFieldException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 根据文件名判断excel版本 获取Workbook对象
     *
     * @param fileName    文件名
     * @param inputStream 输入流
     * @return WorkBook对象
     * @throws IOException 文件名称错误抛出异常
     */
    private static Workbook getWorkBook(String fileName, InputStream inputStream) throws IOException {
        // 创建Workbook工作薄对象
        // 根据文件后缀名不同(xls和xlsx)获得不同的Workbook实现类对象
        if (fileName.endsWith(XLS)) {
            // 2003
            return new HSSFWorkbook(inputStream);
        } else if (fileName.endsWith(XLSX)) {
            // 2007
            return new XSSFWorkbook(inputStream);
        } else {
            throw new IOException("文件名后缀错误");
        }
    }

    /**
     * 每读取一行,就调用execute方法
     */
    public interface ExcelRowsHandler<T> {

        /**
         * 可以在此方法校验数据,校验通过后封装 T,再返回,读入完后,再批量insert/update
         *
         * @param lineNum 行号
         * @param t       解析excel每行后封装好的对象
         * @return 自定义封装成 T,返回null则不记录到list
         */
        ErrorRow execute(int lineNum, T t);
    }


    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class ErrorRow {

        private Integer column;

        private Integer row;

        private String cause;

    }

    @Data
    public static class ParseExcelResult {

        /**
         * 错误行列表
         */
        private List<ErrorRow> errorRowList;

        /**
         * 处理行数
         */
        private int processedRowNum;

        /**
         * 错误行数
         */
        private int errorRowNum;

        /**
         * 空行
         */
        private int emptyRowNum;

        /**
         * 总行数
         */
        private int totalRowNum;

    }
}

创建excel 中 row 对应的 entity ,解析后返回entity
cellName,为单元格头行名称
nullAble,为单元格是否可空
type,表明单元格的数据类型
method,为持久层框架需要调用的方法名称
repositoryClass,为持久层框架的类名


import org.springframework.data.jpa.repository.JpaRepository;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author yaku
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelCheck {

    String cellName();

    boolean nullAble() default false;

    Type type() default Type.STRING;

    String methodName() default "";

    Class<? extends JpaRepository> repositoryClass() default JpaRepository.class;

    enum Type{
        /**
         * 数字
         */
        NUMBER,
        /**
         * 日期时间
         */
        DATETIME,
        /**
         * 字符串
         */
        STRING,
        /**
         * 外键引用,需要配合持久层使用
         */
        LINK
    }

}

这部分为使用反射获取获取 get entity的值和 set entity的值


import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class FieldReflectUtil {

    public static final String GET_STR = "get";
    public static final String IS_STR = "is";
    public static final String SET_STR = "set";

    /**
     * 获得 t.fieldName 的值
     * @param t
     * 操作对象的实例
     * @param fieldName
     * 属性名
     * @param <T>
     * 操作对象的类型
     * @return
     * 获得到的值
     * @throws NoSuchFieldException
     * 找不到对应属性时会抛出该异常
     * @throws IllegalAccessException
     * 如果该工具类没有权限访问该类型对象的时候会抛出该异常
     * @throws NoSuchMethodException
     * 在没有权限访问,且没有getter的情况下会抛出该异常
     * @throws InvocationTargetException
     * 访问setter的时候如果setter抛出了异常则会抛出该异常
     *
     */
    public static <T> Object getFieldValue(T t, String fieldName) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class<?> tClass=t.getClass();
        Field declaredField = tClass.getDeclaredField(fieldName);

        int modifiers = declaredField.getModifiers();
        if (Modifier.isPublic(modifiers)) {
            return declaredField.get(t);
        }

        String name = declaredField.getName();
        String methodName;
        if (declaredField.getType().equals(boolean.class)) {
            methodName = IS_STR;
        } else {
            methodName = GET_STR;
        }
        methodName=buildMethodName(methodName,name);

        Method method = tClass.getMethod(methodName);
        return method.invoke(t);
    }

    /**
     * 设置 t.fieldName 的值
     * 操作对象的类型
     * @param t
     * 操作对象的实例
     * @param fieldName
     * 属性名
     * @param value
     * 设置的值
     * @param <T>
     * 操作对象的类型
     * @throws NoSuchFieldException
     * 找不到对应属性时会抛出该异常
     * @throws IllegalAccessException
     * 如果该工具类没有访问该类型对象的时候会抛出该异常
     * @throws NoSuchMethodException
     * 在没有权限访问,且没有setter的情况下会抛出该异常
     * @throws InvocationTargetException
     * 执行setter的时候如果setter抛出了异常则会抛出该异常
     */
    public static <T> void setFieldValue(T t, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class<?> tClass=t.getClass();
        Field declaredField = tClass.getDeclaredField(fieldName);
        int modifiers = declaredField.getModifiers();
        //是公共的且可修改
        if (Modifier.isPublic(modifiers)&&!Modifier.isFinal(modifiers)) {
            declaredField.set(t, value);
        }

        String name = declaredField.getName();
        String methodName = SET_STR;

        methodName=buildMethodName(methodName,fieldName);

        Method method = tClass.getMethod(methodName, declaredField.getType());
        method.invoke(t, value);
    }

}

最后这部分如何使用,使用lambda代理内部匿名类,获取到的lineNum为当前行号,o为当前成生成的实体,这里可以取出entity的值进行 持久化操作

    @Test
    public void checkExcelHandle() throws IOException {
        File file = new File("xxx");
        FileInputStream input = new FileInputStream(file);
        MultipartFile multipartFile = new MockMultipartFile("file", file.getName(), "text/plain", IOUtils.toByteArray(input));
        ExcelUtil.ParseExcelResult parseExcelResult = ExcelUtil.readExcel(multipartFile, MatchVehicleItem.class, (ExcelUtil.ExcelRowsHandler) (lineNum, o) -> {
            System.out.println(o);
            return null;
        });
        System.out.println(parseExcelResult);
    }

目前还没有整体写完,加上最后的异常处理,错误行记录生成错误行excel,并在错误cell中红色背景并提示错误原因

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
这是一个简单的Spring Boot整合POI解析Excel的示例代码: 首先,需要在pom.xml文件中添加POI的依赖: ```xml <dependencies> <!-- Spring Boot Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- Apache POI --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>4.1.2</version> </dependency> </dependencies> ``` 接下来,创建一个Controller类来处理Excel文件的上传和解析: ```java import org.apache.poi.ss.usermodel.*; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.util.ArrayList; import java.util.List; @Controller public class ExcelController { @PostMapping("/upload") public ResponseEntity<List<String>> uploadExcel(@RequestParam("file") MultipartFile file) { List<String> data = new ArrayList<>(); try { Workbook workbook = WorkbookFactory.create(file.getInputStream()); Sheet sheet = workbook.getSheetAt(0); for (Row row : sheet) { for (Cell cell : row) { data.add(cell.toString()); } } workbook.close(); } catch (IOException e) { e.printStackTrace(); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } return new ResponseEntity<>(data, HttpStatus.OK); } } ``` 在这个示例中,我们使用`WorkbookFactory`来创建Workbook对象,然后获取第一个Sheet,并遍历所有的行和单元格来获取数据。最后,将解析的数据返回给客户端。 注意:这里只是一个简单的示例,实际项目中可能需要根据Excel的具体格式进行更复杂的解析逻辑。 希望以上示例能够帮助你理解如何在Spring Boot中整合POI进行Excel解析

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值