java导入导出excel(支持复杂标题与动态属性的导出和大量数据的导入)


直奔主题,源码下载地址
在gitee上我有附上word使用教程,在这里简单展示一下测试效果以及小细节。

一、普通导出

1.在实体类中给要导出的属性加注解:

/**
 * 测试嵌套导出实体
 * @author ren
 * @description
 * @date 2021年12月12日 15:03:13
 */
@Excel("学生基本信息")
public class TestNestedExport {
    private Long id;

    /** 学号 */
    @Excel(name="学号",sort = 1)
    private String studentNumber;
    /** 姓名 */
    @Excel(value = "姓名",sort = 2)
    private String studentName;
    /** 年龄 */
    @Excel(value = "年龄",cellType = Excel.ColumnType.NUMERIC,sort = 3)
    private Integer age;
    /** 性别 */
    @Excel(value = "性别",sort = 4,converSingleMatchupExp = {"0:男","1:女"})
    private Byte sex;

    /** 测试子级*/
    @Excel(name = "测试子级", hasChildren = true, sort = 5)
    private TextChild textChild;
	
	//这里get和set省略
}
/**
 * 测试复杂标题的导出实体类中的子级
 * @author ren
 * @date 2021年08月23日 09:33:58
 */
public class TextChild {
    /**
     * 创建时间
     */
    @Excel(name = "创建时间", dateFormat = "yyyy-MM-dd HH:mm:ss", width = 20, sort = 6)
    private Date createDate;
    /**
     * 备注
     */
    @Excel(name = "备注", sort = 7)
    private String libraryNote;
    /**
     * 子级中的子级
     */
    @Excel(name = "子级中的子级", hasChildren = true, sort = 8)
    private ChildText childText;

    //这里get和set省略
}
/**
 * 测试复杂标题的导出实体类中子级的子级
 * @author ren
 * @date 2021年08月23日 09:44:22
 */
public class ChildText {
    /**
     * 标题1
     */
    @Excel(value = "标题1",sort = 9)
    private String title1;
    /**
     * title2
     */
    @Excel(sort = 10)
    private String title2;

    //这里get和set省略
}

2.在导出的方法中调用API:

/**
     * 导出数据
     * @param response
     * @return
     */
    @GetMapping("/export")
    @ResponseBody
    public void export(HttpServletResponse response) throws Exception {
        //模拟从数据库查询到的数据,为null时默认为导出数据模板,生成标题没有数据内容
        List<TestNestedExport> list = null;
        ExcelUtils.exportExcel(TestNestedExport.class, list, "测试", "测试导出.xlsx", response,new MyStyles());
    }
    
 class MyStyles extends CustomizeStyles{
    @Override
    protected CellStyle createTitleStyles(Workbook wb) {
        CellStyle style = wb.createCellStyle();
        //设置单元格水平对齐方式
        style.setAlignment(HorizontalAlignment.CENTER);
        //设置单元格垂直对齐方式
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        //设置右边框
        style.setBorderRight(BorderStyle.MEDIUM);
        //设置右边框颜色
        style.setRightBorderColor(IndexedColors.BLACK.getIndex());
        //设置左边框
        style.setBorderLeft(BorderStyle.MEDIUM);
        //设置左边框颜色
        style.setLeftBorderColor(IndexedColors.BLACK.getIndex());
        //设置上边框
        style.setBorderTop(BorderStyle.MEDIUM);
        //设置上边框颜色
        style.setTopBorderColor(IndexedColors.BLACK.getIndex());
        //设置下边框
        style.setBorderBottom(BorderStyle.MEDIUM);
        //设置下边框颜色
        style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
        //设置前景颜色
        style.setFillForegroundColor(IndexedColors.LIGHT_ORANGE.getIndex());
        //设置前景的风格样式
        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        //设置字体样式
        Font headerFont = wb.createFont();
        headerFont.setFontName("Arial");
        headerFont.setFontHeightInPoints((short) 10);
        headerFont.setBold(true);
        headerFont.setColor(IndexedColors.BLACK.getIndex());
        style.setFont(headerFont);
        return style;
    }

    @Override
    protected CellStyle createDataStyles(Workbook wb) {
        CellStyle style = wb.createCellStyle();
        //设置单元格水平对齐方式
        style.setAlignment(HorizontalAlignment.CENTER);
        //设置单元格垂直对齐方式
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        //设置右边框
        style.setBorderRight(BorderStyle.THIN);
        //设置右边框颜色
        style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        //设置左边框
        style.setBorderLeft(BorderStyle.THIN);
        //设置左边框颜色
        style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        //设置上边框
        style.setBorderTop(BorderStyle.THIN);
        //设置上边框颜色
        style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        //设置下边框
        style.setBorderBottom(BorderStyle.THIN);
        //设置下边框颜色
        style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        //设置字体样式
        Font dataFont = wb.createFont();
        dataFont.setFontName("Arial");
        dataFont.setFontHeightInPoints((short) 10);
        style.setFont(dataFont);
        return style;
    }

    @Override
    protected CellStyle createTotalStyles(Workbook wb) {
        CellStyle style = wb.createCellStyle();
        //设置单元格水平对齐方式
        style.setAlignment(HorizontalAlignment.RIGHT);
        //设置单元格垂直对齐方式
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        //设置右边框
        style.setBorderRight(BorderStyle.THIN);
        //设置右边框颜色
        style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        //设置左边框
        style.setBorderLeft(BorderStyle.THIN);
        //设置左边框颜色
        style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        //设置上边框
        style.setBorderTop(BorderStyle.THIN);
        //设置上边框颜色
        style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        //设置下边框
        style.setBorderBottom(BorderStyle.THIN);
        //设置下边框颜色
        style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        //设置字体样式
        Font totalFont = wb.createFont();
        totalFont.setFontName("Arial");
        totalFont.setFontHeightInPoints((short) 10);
        style.setFont(totalFont);
        return style;
    }
}
这里最后一个参数MyStyles (),是设置导出表格的样式的,可以不传这个参数,框架有默认的样式风格。

3.导出效果:

在这里插入图片描述

可以看出如果没有给注解设置标题值,框架默认取属性名作为标题名。注解中的hasChildren = true是设置是否存在子级的,这是创建复杂标题的关键,这里要注意的是,当hasChildren = true时,对应的属性值不能等于null,也就是说这里textChild != null。还有一种简单的创建复杂标题的方式,如下所示:

/**
 * 测试常规导出实体
 * @author ren
 * @description
 * @date 2021年12月12日 15:03:13
 */
@Excel("学生基本信息")
public class TestNestedExport {
    private Long id;

    /** 学号 */
    @Excel(name="学号",sort = 1)
    private String studentNumber;
    /** 姓名 */
    @Excel(value = "姓名",sort = 2)
    private String studentName;
    /** 年龄 */
    @Excel(value = "年龄",cellType = Excel.ColumnType.NUMERIC,sort = 3)
    private Integer age;
    /** 性别 */
    @Excel(value = "性别",sort = 4,converSingleMatchupExp = {"0:男","1:女"})
    private Byte sex;

    /** 创建时间 */
    @Excel(name = "测试子级^创建时间", dateFormat = "yyyy-MM-dd HH:mm:ss", width = 20, sort = 5)
    private Date createDate;
    /** 备注 */
    @Excel(name = "测试子级^备注", sort = 6)
    private String libraryNote;

    /** 标题1 */
    @Excel(value = "测试子级^子级中的子级^标题1",sort = 7)
    private String title1;

    /** title2*/
    @Excel(name="测试子级^子级中的子级^title2",sort = 8)
    private String title2;
    
	//这里get和set省略
}
这样创建出的复杂标题和上面那种类嵌套的形式效果一样,这种写法更加简单便捷,官方(也就是我,嘻嘻!)推荐这种写法。

二、动态导出

以上的方式就足以应对常规的导出,但是它有一个通病,假设有多个导出方法,导出的数据都是用的同一个类作为模板,但不同的方法需要导出的字段不同;或者同一个导出方法由前端控制具体导出哪些字段,也就是说具体要导出类中的哪些属性是不确定的,只有在执行方法时才能确定。面对这种情况好像就有点力不存心了。于是强大的2.0版本来了。 这里要说明一下,我查阅过好多博客都没有这个功能,好多同学都在这个地方折戟。那么就先演示吧:

1.在实体类中注解加属性isDynamicField = true:

/**
 * 测试常规导出实体
 * @author ren
 * @description
 * @date 2021年12月12日 15:03:13
 */
@Excel("学生基本信息")
public class TestNestedExport {
    private Long id;

    /** 学号 */
    @Excel(name="学号",sort = 1,isDynamicField = true)
    private String studentNumber;
    /** 姓名 */
    @Excel(value = "姓名",sort = 2)
    private String studentName;
    /** 年龄 */
    @Excel(value = "年龄",cellType = Excel.ColumnType.NUMERIC,sort = 3)
    private Integer age;
    /** 性别 */
    @Excel(value = "性别",sort = 4,converSingleMatchupExp = {"0:男","1:女"},isDynamicField = true)
    private Byte sex;

    /** 创建时间 */
    @Excel(name = "测试子级^创建时间", dateFormat = "yyyy-MM-dd HH:mm:ss", width = 20, sort = 5)
    private Date createDate;
    /** 备注 */
    @Excel(name = "测试子级^备注", sort = 6)
    private String libraryNote;

    /** 标题1 */
    @Excel(value = "测试子级^子级中的子级^标题1",sort = 7)
    private String title1;

    /** title2*/
    @Excel(name="测试子级^子级中的子级^title2",sort = 8)
    private String title2;
    
	//这里get和set省略
}

2.在导出的方法中调用API时加参数new BlackList(list):

/**
     * 导出数据
     * @param response
     * @return
     */
    @GetMapping("/export")
    @ResponseBody
    public void export(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //模拟从数据库查询到的数据,为null时默认为导出数据模板,生成标题没有数据内容
        List<TestNestedExport> list = null;
        //从前端获取要导出动态属性的哪些
        //List<String> temp = Arrays.asList(request.getParameter("exportFields").split(","));
        //这里假设前端不需要导出动态属性中的studentNumber,或者这个导出方法只需要导出动态属性中的sex属性就好
        String[] temp = new String[]{"studentNumber"};
        ExcelUtils.exportExcel(TestNestedExport.class, list, "测试", "测试导出.xlsx", response, new MyStyles(),new BlackList(temp));
    }
可以看到这里多了一个返回属性黑名单的参数。还有一种方法是返回属性白名单,使用方式如下:
/**
     * 导出数据
     * @param response
     * @return
     */
    @GetMapping("/export")
    @ResponseBody
    public void export(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //模拟从数据库查询到的数据,为null时默认为导出数据模板,生成标题没有数据内容
        List<TestNestedExport> list = null;
        //从前端获取要导出动态属性的哪些
        //List<String> temp = Arrays.asList(request.getParameter("exportFields").split(","));
        //这里假设前端只需要导出动态属性中的sex,或者这个导出方法不需要导出动态属性中的studentNumber属性
        String[] temp = new String[]{"sex"};
        ExcelUtils.exportExcel(TestNestedExport.class, list, "测试", "测试导出.xlsx", response,new MyStyles(),new WhiteList(temp));
    }
注意这里的最后一个参数,WhiteList()和BlackList()是框架自己定义的,跟我们常用的集合没有关系,但是其内部方法的用法跟我们常用的集合一样。还有一点,这里过滤的属性只对最底层的属性起作用,就是说注解上有hasChildren = true的在这里过滤是不起作用的,所以要想这个类里边的所有属性都不导出,只能把这些属性都设置成动态属性,添加到黑名单中或者不要放入白名单中。

3.导出效果:

在这里插入图片描述
可以看到虽然类中的属性加上了excel注解,但这里我们只导出了动态属性中的性别属性。哦,对了,导出操作还会检索父一级的注解(有的同学设计会把创建时间等每个类都共用的属性题到父类中去),但出于性能考虑只检索一级父类。也就是说导出类的直接父类中的属性加注解也是可以导出的。

三、导入

1.要导入的文档:

在这里插入图片描述

2.承接数据的java类

public class TestNestedExport {
    private Long id;

    /** 学号 */
    @Excel(name="学号",sort = 1,isDynamicField = true)
    private String studentNumber;
    /** 姓名 */
    @Excel(value = "姓名",sort = 2)
    private String studentName;
    /** 年龄 */
    @Excel(value = "年龄",cellType = Excel.ColumnType.NUMERIC,sort = 3)
    private String age;
    /** 性别 */
    @Excel(value = "性别",sort = 4,converSingleMatchupExp = {"0:男","1:女"},isDynamicField = true)
    private String sex;

    /** 创建时间 */
    @Excel(name = "测试子级^创建时间", dateFormat = "yyyy-MM-dd HH:mm:ss", width = 20, sort = 5)
    private Date createDate;
    /** 备注 */
    @Excel(name = "测试子级^备注", sort = 6)
    private String libraryNote;

    /** 标题1 */
    @Excel(value = "测试子级^子级中的子级^标题1",sort = 7)
    private String title1;

    /** title2*/
    @Excel(name="测试子级^子级中的子级^title2",sort = 8)
    private String title2;

    //geter和seter略
    //toString略
}

3.调用导入API(这里在main方法测试就可以):

public static void main(String[] args) throws Exception {
        File f = new File("C:\\Users\\Administrator\\Desktop\\测试导出.xlsx");
        InputStream is = new FileInputStream(f);
        ImportResult result = ExcelUtils.importExcel(is, TestNestedExport.class,4, new ExcelImportHandler<TestNestedExport>() {
            @Override
            public void eachColumnCallBack(Sheet sheet, int rowNum, Row row, int column, Cell cell, TestNestedExport vo, Object value) throws Exception {
                System.out.println("执行每列的回调,当前是第" + (rowNum + 1) + "行" + (column + 1) + "列,得到值为:"+value);
                switch (column){
                    case 0:vo.setStudentNumber(value == null?"":value.toString());break;
                    case 1:vo.setStudentName(value == null?"":value.toString());break;
                    case 2:vo.setAge(value == null?"":value.toString());break;
                    case 3:
                        String sex = null;
                        if(StringUtils.isNotNull(value)){
                            sex = value == null?"":value.toString();
                            switch (sex){
                                case "男": sex = "0";break;
                                case "女": sex = "1";break;
                                default: throw new Exception("没有此性别状态。");
                            }
                        }
                        vo.setSex(sex);break;
                    case 4:vo.setCreateDate(DateUtils.strToDate(value == null?"":value.toString(),"yyyy-MM-dd HH:mm:ss"));break;
                    case 5:vo.setLibraryNote(value == null?"":value.toString());break;
                    case 6:vo.setTitle1(value == null?"":value.toString());break;
                    case 7:vo.setTitle2(value == null?"":value.toString());break;
                    default:throw new Exception("没有此索引列相关操作,请检查导入模板是否正确。");
                }
            }

            @Override
            public TestNestedExport eachRowCallBack(Sheet sheet, int rowNum, Row row, TestNestedExport vo) {
                //这里是执行每行的回调,在这里可以已经把每行的数据转化成了java对象,可在这里检查数据是否正确也可以赋值导入数据之外的其他属性
                System.out.println("执行每行的回调,当前是第" + (rowNum + 1) + "行,处理对象:" + vo.toString());
                return vo;
            }

            @Override
            public void batchSaveCallBack(List<TestNestedExport> cacheList) throws Exception {
                System.out.println("执行用户保存的回调,在这里模拟保存数据,集合中的数据为:");
                for (TestNestedExport item : cacheList) {
                    System.out.println(item.toString());
                }
            }
        });
        System.out.println(result.toString());
    }

4.执行结果:

[main] INFO org.kangjia.importFile.ImportExcel - ==> 正在初始化参数...
[main] INFO org.kangjia.importFile.ImportExcel - ==> 参数初始化完成...
[main] INFO org.kangjia.importFile.ImportExcel - ==> 开始执行导入操作...
执行每列的回调,当前是第51列,得到值为:1.0
执行每列的回调,当前是第52列,得到值为:小明
执行每列的回调,当前是第53列,得到值为:18.0
执行每列的回调,当前是第54列,得到值为:男
执行每列的回调,当前是第55列,得到值为:2021-12-10 10:12:25
执行每列的回调,当前是第56列,得到值为:新增小明
执行每列的回调,当前是第57列,得到值为:啊啊啊
执行每行的回调,当前是第5行,处理对象:TestNestedExport{id=null, studentNumber='1.0', studentName='小明', age=18.0, sex=0, createDate=Fri Dec 10 10:12:25 CST 2021, libraryNote='新增小明', title1='啊啊啊', title2='null'}
执行每列的回调,当前是第61列,得到值为:2.0
执行每列的回调,当前是第62列,得到值为:小红
执行每列的回调,当前是第63列,得到值为:17.0
执行每列的回调,当前是第64列,得到值为:女
执行每列的回调,当前是第65列,得到值为:2021-12-10 10:12:25
执行每列的回调,当前是第66列,得到值为:新增小红
执行每列的回调,当前是第67列,得到值为:null
执行每列的回调,当前是第68列,得到值为:哦哦哦
执行每行的回调,当前是第6行,处理对象:TestNestedExport{id=null, studentNumber='2.0', studentName='小红', age=17.0, sex=1, createDate=Fri Dec 10 10:12:25 CST 2021, libraryNote='新增小红', title1='', title2='哦哦哦'}
执行每列的回调,当前是第71列,得到值为:3.0
执行每列的回调,当前是第72列,得到值为:小刚
执行每列的回调,当前是第73列,得到值为:19.0
执行每列的回调,当前是第74列,得到值为:男
执行每列的回调,当前是第75列,得到值为:2021-12-10 10:12:25
执行每列的回调,当前是第76列,得到值为:新增小刚
执行每列的回调,当前是第77列,得到值为:呃呃呃
执行每行的回调,当前是第7行,处理对象:TestNestedExport{id=null, studentNumber='3.0', studentName='小刚', age=19.0, sex=0, createDate=Fri Dec 10 10:12:25 CST 2021, libraryNote='新增小刚', title1='呃呃呃', title2='null'}
执行每列的回调,当前是第81列,得到值为:4.0
执行每列的回调,当前是第82列,得到值为:小强
执行每列的回调,当前是第83列,得到值为:20.0
执行每列的回调,当前是第84列,得到值为:男
执行每列的回调,当前是第85列,得到值为:2021-12-10 10:12:25
执行每列的回调,当前是第86列,得到值为:新增小强
执行每列的回调,当前是第87列,得到值为:null
执行每列的回调,当前是第88列,得到值为:嘤嘤嘤
执行每行的回调,当前是第8行,处理对象:TestNestedExport{id=null, studentNumber='4.0', studentName='小强', age=20.0, sex=0, createDate=Fri Dec 10 10:12:25 CST 2021, libraryNote='新增小强', title1='', title2='嘤嘤嘤'}
执行用户保存的回调,在这里模拟保存数据,集合中的数据为:
TestNestedExport{id=null, studentNumber='1.0', studentName='小明', age=18.0, sex=0, createDate=Fri Dec 10 10:12:25 CST 2021, libraryNote='新增小明', title1='啊啊啊', title2='null'}
TestNestedExport{id=null, studentNumber='2.0', studentName='小红', age=17.0, sex=1, createDate=Fri Dec 10 10:12:25 CST 2021, libraryNote='新增小红', title1='', title2='哦哦哦'}
TestNestedExport{id=null, studentNumber='3.0', studentName='小刚', age=19.0, sex=0, createDate=Fri Dec 10 10:12:25 CST 2021, libraryNote='新增小刚', title1='呃呃呃', title2='null'}
TestNestedExport{id=null, studentNumber='4.0', studentName='小强', age=20.0, sex=0, createDate=Fri Dec 10 10:12:25 CST 2021, libraryNote='新增小强', title1='', title2='嘤嘤嘤'}
[main] INFO org.kangjia.importFile.ImportExcel - ==> 导出操作执行完成,总耗时31毫秒...
ImportResult{code=SUCCESS, errorCount=0, errorRecord=[], errorRow=null, successCount=4, successRow=[5, 6, 7, 8], elapsedTime=31}

Process finished with exit code 0

5.导入操作的保护机制:

在导入操作中考虑到性能问题,读取一定量数据时会触发一次保存操作,这样就实现了边读边存,框架默认是每100条数据保存一次(当导入大量数据时可以把这个阈值调大)。出于数据完整性考虑,提出了保存出错阈值机制(框架默认为5),当连续5次保存数据都出错时终止导入操作。

四、总结

最后,关于API的其他参数以及注解的属性说明在我的gitee源码中的文档中都有详细记载。此框架使用的日志框架为log4j,默认的日志级别为info,想看更细节的日志可以在你的配置文件中这样配置:

logging.level.org.kangjia=debug
哦!对了,我源码里面几乎每一步都有详细的注释,有兴趣的同学可以研究研究。可以多提提意见。
  • 11
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
您好!关于您的问题,我可以为您提供一些Java导入导出Excel的操作方法。 1. 使用Apache POI库进行Excel文件的读写操作。您需要添加相关的依赖,然后可以使用POI API进行Excel文件的读写操作。下面是读取Excel文件的示例代码: ``` FileInputStream file = new FileInputStream(new File("excel文件的路径")); Workbook workbook = new XSSFWorkbook(file); Sheet sheet = workbook.getSheetAt(0); for (Row row : sheet) { for (Cell cell : row) { switch (cell.getCellType()) { case STRING: System.out.print(cell.getStringCellValue() + "\t"); break; case NUMERIC: System.out.print(cell.getNumericCellValue() + "\t"); break; case BOOLEAN: System.out.print(cell.getBooleanCellValue() + "\t"); break; default: break; } } System.out.println(); } workbook.close(); file.close(); ``` 2. 使用EasyPOI库进行Excel文件的读写操作。EasyPOI是基于POI封装的Excel文件操作库,提供了更加简单方便的API,可以快速地进行Excel文件的读写操作。下面是一个简单的示例代码: ``` // 导入Excel文件 List<ExcelModel> list = ExcelImportUtil.importExcel( new File("excel文件的路径"), ExcelModel.class, new ImportParams()); for (ExcelModel data : list) { System.out.println(data.toString()); } // 导出Excel文件 List<ExcelModel> list = new ArrayList<>(); // 添加数据到list中 ExcelExportUtil.exportExcel(new ExportParams("sheet1", "测试"), ExcelModel.class, list, new FileOutputStream("excel文件的路径")); ``` 以上是两种常见的Java操作Excel文件的方法。希望能够帮助到您!
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值