使用EasyExcel实现excel数据导入功能

在使用Spring Boot框架开发系统时,难免会碰到excel导入导出的功能,本文将借助EasyExcel包,实现excel数据的导入导出功能。

1.实现思路

1.定义导入数据的model

在里面添加校验注解和导入列的字段名,本文使用java自带的校验注解。使用分组验证,统一为数据导入数据库接口。

2.编写监听器

在监听器里面实现数据的校验、设置导入数据的默认值、模版数据与数据库实体数据的转换。

3.编写导入拓展类

在里面实现excel导入功能和模板下载功能。

4.定义数据导入数据库接口

使得excel导入功能和spring bean有关联,在相应的service接口中实现该接口。

5.定义excel导入工厂类

每次从工厂获得监听器对象和excel导入拓展对象。

2.具体步骤

1.导入依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.2.0</version>
</dependency>

2.编写model类

本文使用javax.validation包下的注解进行简单校验,如果业务复杂,则可以导入Hibernate Validator包,@ExcelProperty注解是EasyExcel包自带注解,用来确定导入excel表格的列名,groups则为分组参数。

@Data
public class UserImportModel{
 /**
     * 用户名
     */
    @NotBlank(message = "用户名不能为空", groups = {ExcelImportService.class})
    @ExcelProperty(value = "用户名")
    private String userName;
    /**
     * 联系电话
     */
    @NotBlank(message = "联系电话不能为空", groups = {ExcelImportService.class})
    @ExcelProperty(value = "联系电话")
    private String phone;
}

2.编写监听器类

监听器类中主要定义了数据校验、设置默认值、数据转换方法,该方法在使用时可根据实际业务需要重写。转换后的数据均存储在dataList列表,可通过getDataList获取,在初始化监听器时需要传入数据校验器,泛型M对应模型类,E对应实体类,源码附上。

public class ReadDataListener<M, E> implements ReadListener<M> {
    /**
     * 数据列表
     */
    private List<E> dataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

    /**
     * 验证器
     */
    private final Validator validator;
    /**
     * 当前处理行号
     */
    private long currentRowNo = 1;
    private static final Logger LOGGER = LoggerFactory.getLogger(TianHengServiceImpl.class);
    /**
     * 获取当前行号
     *
     * @return long 行号
     */
    public long getCurrentRowNo() {
        return currentRowNo;
    }
    /**
     * 构造方法
     * 每次创建Listener的时候需要把spring管理的类传进来
     *
     */
    public ReadDataListener(Validator validator) {
        this.validator = validator;
    }

    /**
     * 解析数据
     *
     * @param data    单行记录
     * @param context 解析上下文
     */
    @Override
    public void invoke(M data, AnalysisContext context) {
        // 处理数据
        E entity = handleData(data);
        // 添加数据
        dataList.add(entity);
        currentRowNo++;
    }

    /**
     * 所有数据解析完成了 就会来调用
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        LOGGER.info("所有数据解析完成!");
    }

    /**
     * 数据预处理,可转化实体类中的字典数据,也可以设置默认字段
     *
     * @param vo 视图对象
     */
    public E handleData(M vo) {
        // 数据校验
        validateData(vo);
        // 设置默认值
        setDefaultValue(vo);
        // 数据转换
        return convertData(vo);
    }

    private void validateData(M model) {
        Set<ConstraintViolation<M>> violations = validator.validate(model, ExcelImportService.class);
        if (!violations.isEmpty()) {
            StringBuilder sb = new StringBuilder();
            for (ConstraintViolation<M> violation : violations) {
                sb.append(violation.getMessage()).append("\n");
            }
            throw new OuterException(RetCode.BAD_REQUEST, sb.toString());
        }
    }


    /**
     * 设置默认值
     *
     * @param model 模型对象
     */
    protected void setDefaultValue(M model) {
        // 如无需设置,则该方法可为空
    }


    /**
     * 转换数据
     *
     * @param model 模型对象
     * @return {@link E} 数据库实体对象
     */
    protected E convertData(M model) {
        throw new InnerException(RetCode.INTERNAL_SERVER_ERROR,"未实现convertData方法");
    }


    public List<E> getDataList() {
        return dataList;
    }
}

3.编写导入拓展类

        该类实现了excel导入功能,以及模板下载功能,泛型M对应模型Model类,E对应数据库实体类。

初始化时需要传入保存数据的spring Bean,导入监听器,导入模板地址。

downloadImportTemplate方法

        为下载导入模板,通过模版地址,获取模板输入流,将输入流拷贝到response输出流中,再设置响应头等信息。

importExcel

        excel数据导入功能,做excel文件类型校验,获取文件输入流,通过EasyExcel.read(inputStream, modelClass, this.readListener).sheet().doRead();方法读取excel数据,数据会被添加到监听器中的dataList中,在catch里面做了异常捕获,可详细捕获异常数据所在的行。最后调用所传入spring bean的saveAll方法,保存所有数据到数据库中。

public class ExcelImportExtension<M, E>{
    /**
     * 数据监听器
     */
    private ReadDataListener readListener;

    /**
     * 业务接口
     */
    private final ExcelImportService<E> service;
    /**
     * 导入模板
     */
    private String importTemplate;


    public ExcelImportExtension(ExcelImportService<E> service, ReadDataListener<M, E> readDataListener, String templatePath) {
        this.service = service;
        this.readListener = readDataListener;
        this.importTemplate = templatePath;
    }


    public void setReadListener(ReadDataListener readListener) {
        this.readListener = readListener;
    }
    /**
     * 设置导入模板
     */
    protected void setImportTemplate(String importTemplate) {
        this.importTemplate = importTemplate;
    }

    /**
     * 下载导入模板
     */
    public void downloadImportTemplate(HttpServletResponse response) {
        if (!StringUtils.hasText(importTemplate)) {
            throw new OuterException(RetCode.BAD_REQUEST,"请设置导入模板");
        }
        ClassPathResource classPathResource = new ClassPathResource(importTemplate);
        try (InputStream inputStream = classPathResource.getInputStream();
             OutputStream outputStream = response.getOutputStream()) {
            // 设置响应信息
            response.setContentType("application/vnd.ms-excel");
            response.setCharacterEncoding("utf-8");
            // 这里URLEncoder.encode可以防止中文乱码
            String fileName = URLEncoder.encode(importTemplate, "UTF-8");
            response.setHeader("Content-disposition", "attachment;filename=" + fileName);
            IOUtils.copy(inputStream, outputStream);
        } catch (Exception exception) {
            throw new OuterException(RetCode.BAD_REQUEST,"下载导入模板失败");
        }

    }

    /**
     * excel数据导入
     * @param file 上传的文件
     * return 是否导入成功
     */
    @Transactional(rollbackFor = Exception.class)
    public Boolean importExcel(MultipartFile file, Class<M> modelClass) {
        String[] excelType = {"xls", "xlsx"};
        if (file.isEmpty()) {
            throw new OuterException(RetCode.BAD_REQUEST, "文件不能为空");
        }
        String filename = file.getOriginalFilename();
        assert filename != null;
        if (!filename.endsWith(excelType[0]) && !filename.endsWith(excelType[1])) {
            throw new OuterException(RetCode.BAD_REQUEST, "excel文件格式不正确");
        }
        try(InputStream inputStream = file.getInputStream()) {
            EasyExcel.read(inputStream, modelClass, this.readListener).sheet().doRead();
        } catch (Exception exception) {
            long currentRowNo = this.readListener.getCurrentRowNo();
            Throwable throwable = exception;
            while (throwable.getCause() != null) {
                throwable = throwable.getCause();

            }
            throw new OuterException(RetCode.BAD_REQUEST,"第"+currentRowNo+"条数据错误,"+throwable.getMessage());
        }
        //保存所有数据
        return service.saveAll(this.readListener.getDataList());
    }

4.定义数据导入数据库接口

该接口用于将转换好的数据保存到数据库,使用excel导入功能的bean必须实现该接口。

public interface ExcelImportService<T> {
    /**
     * 批量保存数据到数据库
     * @param items 批量数据
     * @return 保存结果
     */
    Boolean saveAll(List<T> items);
}

5.定义导入工厂

工厂支持获取拓展类对象以及监听器对象,在获取监听器对象时,要传入类型转换方法,针对不同的模板和实体有不同的转换方法,所有没有设置默认转换方法。

public class ImportFactory {
    /**
     * 创建Excel拓展器
     * @param excelImportService 导入器
     * @param <M> 导入模型类
     * @param <E> 数据库实体数据类型
     *@return ExcelImportExtension<M, E> 导入器扩展类
     */
    public static <M, E>ExcelImportExtension createExcelImportExtension(ExcelImportService excelImportService,
                                                                  ReadDataListener readDataListener,
                                                                  String templatePath) {
        return new ExcelImportExtension<M, E>(excelImportService, readDataListener, templatePath){};
    }

    /**
     * 创建读取数据监听器
     * @param validator 验证器
     * @param function 转换函数
     * @return 读取数据监听器
     * @param <M> 导入模型类
     * @param <E> 数据库实体数据类型
     */
    public static <M, E> ReadDataListener<M, E> createReadDataListener(Validator validator, Function<M, E> function) {
        return new ReadDataListener<M, E>(validator) {
            @Override
            public E convertData(M model) {
                return function.apply(model);
            }
        };
    }
}

使用实例

        其中,saveAll方法实现了ExcelImportService<DutyEntity>接口中的saveAll方法,实现数据批量入库。

pulic class UserServiceImpl implement UserService,ExcelImportService<UserEntity> {

       private final UserDao userDao;
       private final Validator validator;
       public UserServiceImpl(UserDao userDao, Validator validator) {
             this.userDao = userDao;
             this.validator = validator;
        }
         @Override
    public Boolean importData(MultipartFile file) {
        ReadDataListener<UserImportModel, UserEntity> readListener = ImportFactory.createReadDataListener(validator, this::modelToEntity);
        ExcelImportExtension excelImportExtension = ImportFactory
            .createExcelImportExtension(this, readListener, "用户导入模板.xlsx");
        excelImportExtension.setReadListener(readListener);
        return excelImportExtension.importExcel(file, UserImportModel.class);
    }

    @Override
    public void downloadTemplate(HttpServletResponse response) {
        ReadDataListener readListener = ImportFactory.createReadDataListener(validator, this::modelToEntity);
        ExcelImportExtension<UserImportModel, UserEntity> excelImportExtension = new ExcelImportExtension<UserImportModel,
            UserEntity>(this, readListener,"用户导入模板.xlsx"){};
        excelImportExtension.downloadImportTemplate(response);
    }
 @Override
    public Boolean saveAll(List<UserEntity> items) {
        if (CollectionUtils.isEmpty(items)) {
            throw new OuterException(RetCode.BAD_REQUEST, "导入数据不能为空");
        }
        int batchSize = 100;
        boolean flag = true;
        for (int i = 0; i < items.size(); i += batchSize) {
            List<UserEntity> subList = items.subList(i, Math.min(i + batchSize, items.size()));
            flag = userDao.insertBatch(subList);
            if (!flag) {
                return flag;
            }
        }
        return flag;
    }
/**
     * 模型转实体
     *
     * @return 实体
     */

private UserEntity modelToEntity(UserImportMoel model) {
        UserEntity userEntity = new UserEntity;
        BeanUtils.copyProperties(userEntity, model);
        return userEntity;
}

}

        以上就是excel数据导入的全部实例及其代码了,有不足之处,还请见谅。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值