java 实现数据导入导出

  • Post author:
  • Post category:java


官方文档(完整功能展示):

地址

依赖

<!-- easyexcel导入导出 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>3.2.1</version>
        </dependency>


导出


1.随便一个实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    @ExcelProperty("用户id")
    private Long uid;
    @ExcelProperty("用户名")
    private String uname;
    @ExcelProperty("用户性别")
    private int usex;
    @ExcelProperty("注册时间")
    private Date udate;
}

2.编一个方法用于返回要导出的数据

//编数据用作导出
    private List<User> data() {
        List<User> list = ListUtils.newArrayList();
        Random random = new Random();
        for (long i = 0; i < 10; i++) {
            User data = new User(i,"用户"+i,random.nextInt(2),new Date());
            list.add(data);
        }
        return list;
    }

3.1 用户访问接口时进行下载

@GetMapping(value = "ceshi")
    public void ceshi(HttpServletResponse response, @Param("fileName") String fileName) throws IOException {
        ServletOutputStream out = response.getOutputStream();
        response.setContentType("multipart/form-data");
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-Disposition", "attachment;filename*=utf-8'zh_cn'" + URLEncoder.encode(fileName + ".xlsx", "UTF-8"));
        EasyExcel.write(out, User.class).sheet("模板").doWrite( data() );//User.class是要到出数据的实体类,data()是要导出的数据
        out.flush();//使用户下载文件
    }

3.2 直接规定一个地址进行保存

@GetMapping(value = "ceshi")
    public void ceshi(){
        String fileName = "E:\\ceshi\\" + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
        EasyExcel.write(fileName, User.class).sheet("模板").doWrite(data());
    }

请求接口方法:

链接


我这里是直接浏览器输入url访问的

测试3.1,效果

在这里插入图片描述

测试3.2,效果

在这里插入图片描述


导入


1.随便一个实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    @ExcelProperty("用户id")
    private Long uid;
    @ExcelProperty("用户名")
    private String uname;
    @ExcelProperty("用户性别")
    private int usex;
    @ExcelProperty("注册时间")
    private Date udate;
}

2.一个dao用于存储至数据库,此处随便写的用于演示

public class DemoDAO {
    public void save(List<User> list){
        System.out.println("调用-------------------------");
        list.forEach(x->{
            System.out.println(x);
        });
    }
}

3.官网复制的监听类


import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import lombok.extern.slf4j.Slf4j;

import java.util.List;

// 有个很重要的点 UserListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
@Slf4j
public class UserListener implements ReadListener<User> {

    /**
     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 5;
    /**
     * 缓存的数据
     */
    private List<User> userDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
    /**
     * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
     */
    private DemoDAO demoDAO;

    public UserListener() {
        // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
        demoDAO = new DemoDAO();
    }

    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     *
     * @param demoDAO
     */
    public UserListener(DemoDAO demoDAO) {
        this.demoDAO = demoDAO;
    }

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(User data, AnalysisContext context) {
        log.info("解析到一条数据:{}");
        userDataList.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (userDataList.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            userDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        log.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        log.info("{}条数据,开始存储数据库!", userDataList.size());
        demoDAO.save(userDataList);
        log.info("存储数据库成功!");
    }
}

4.测试接口

@GetMapping(value = "ceshi1")
    public void ceshi1() {
        String fileName="E:\\ceshi\\1678090270171.xlsx";
        EasyExcel.read(fileName, User.class, new UserListener()).sheet().doRead();
    }

第一个参数也可是输入流,如multipartFile.getInputStream()

在这里插入图片描述

效果
在这里插入图片描述


对于需要字典的字段(基本数据类型封装类写法)


1.修改实体类

在这里插入图片描述

 @ExcelProperty(value = "用户性别",converter = UsexConverter.class)
    private Integer usex;

2.类型装换器

public class UsexConverter  implements Converter<Integer> {

    @Override
    public Class<?> supportJavaTypeKey() {
        return Integer.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.NUMBER;
    }

    @Override
    public Integer convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
        // 导入时字符串转换数字
        switch (cellData.getStringValue()){
            case "男" : return 1;
            case "女" : return 0;
            default: return -1;
        }
    }

    /**
     * 将从数据库中查到的数据转换为 Excel 展示的数据
     * @param value 枚举对象
     */
    @Override
    public WriteCellData<?> convertToExcelData(Integer value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
        String sexName;
        switch (value){
            case 0 : sexName= "女";break;
            case 1 : sexName= "男";break;
            default: sexName="其他";
        }
        return new WriteCellData<>(sexName);
    }
}

导出效果

在这里插入图片描述

导入效果

在这里插入图片描述


对于需要字典的字段(枚举写法)


1.添加枚举类

public enum UsexEnum {
    MAN("男",1),WOMEN("女",0),UNKNOWN("未知",-1);

    String name;
    int value;

    UsexEnum(String name,int value){
        this.name = name;
        this.value = value;
    }
    /*
       根据name值返回枚举类
     */
    public static UsexEnum getUsexEnumofName(String name){
        for (UsexEnum us : UsexEnum.values())
            if (name.equals(us.getName()))
                return us;
        return UNKNOWN;
    }
    String getName(){
        return name;
    }
    int getValue(){
        return value;
    }

    @Override
    public String toString(){
        return name;
    }
}

2.修改实体类

在这里插入图片描述

@ExcelProperty(value = "用户性别",converter = UsexConverter.class)//UsexConverter为类型装换器
    private UsexEnum usex;

3.类型装换器

package Ceshi.daorudaochu;


import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import lombok.extern.slf4j.Slf4j;

/**
 * Excel 性别类型装换器
 *
 * @author wang suo
 * @version 1.0
 * @date 2021/9/14 15:06
 */
@Slf4j
public class UsexConverter  implements Converter<UsexEnum> {

    @Override
    public Class<?> supportJavaTypeKey() {
        return UsexEnum.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return null;
    }

	/**
     *导入触发
     * @param value 枚举对象
     */
    @Override
    public UsexEnum convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
        // 导入时根据表格name值转化为枚举对象
        return UsexEnum.getUsexEnumofName(cellData.getStringValue());
    }

    /**
     * 导出触发,将从数据库中查到的数据转换为 Excel 展示的数据
     * @param value 枚举对象
     */
    @Override
    public WriteCellData<?> convertToExcelData(UsexEnum value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
        // 将枚举类型按照 key 传值
        return new WriteCellData<UsexEnum>(String.valueOf(value));
    }
}

导出效果

在这里插入图片描述

导入效果(里边是空时没触发类型转换)
在这里插入图片描述


导出时添加下拉选项


1.添加自定义拦截器

public class CustomSheetWriteHandler  implements SheetWriteHandler {
    @Override
    public void afterSheetCreate(SheetWriteHandlerContext context) {
        // 区间设置 第一列第一行和第二行的数据。由于第一行是头,所以第一、二行的数据实际上是第二三行
        CellRangeAddressList cellRangeAddressList = new CellRangeAddressList(1, 0, 2, 2);//四个参数分别为起始行号,终止行号,起始列号,终止列号。均从0开始
        DataValidationHelper helper = context.getWriteSheetHolder().getSheet().getDataValidationHelper();
        DataValidationConstraint constraint = helper.createExplicitListConstraint(new String[] {"男", "女"});//下拉框值
        DataValidation dataValidation = helper.createValidation(constraint, cellRangeAddressList);
        context.getWriteSheetHolder().getSheet().addValidationData(dataValidation);
    }
}

2.导出时添加(此处为链式操作,可添加多个拦截器)

.registerWriteHandler(new CustomSheetWriteHandler())

在这里插入图片描述

效果

在这里插入图片描述


导出多个工作表

ExcelWriter excelWriter = EasyExcel.write("E:\\ceshi\\1678090270171.xlsx").build();
  //具体工作表
 WriteSheet writeSheet = EasyExcel.writerSheet("Sheet1").head(xxx.class).build();//head根据实体类确定表头,不加就直接填充数据
excelWriter.write(result,writeSheet);//向表填充数据
 writeSheet = EasyExcel.writerSheet("表2").build();//writerSheet随便起,不是指定要填哪个表而是第二个表叫表2
  excelWriter.write(result2,writeSheet);
  writeSheet = EasyExcel.writerSheet("Sheet3").build();//有几个writeSheet最后的xlsx就有几个工作表,sheel顺序按照代码顺序
  excelWriter.write(result3,writeSheet);
  excelWriter.finish();//关闭流(必须)



版权声明:本文为qq_43840143原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。