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

您所在的位置:网站首页 excel导入多个表数据 java导入导出excel(支持复杂标题与动态属性的导出和大量数据的导入)

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

2024-05-26 11:44| 来源: 网络整理| 查看: 265

文章目录 一、普通导出1.在实体类中给要导出的属性加注解:2.在导出的方法中调用API:3.导出效果: 二、动态导出1.在实体类中注解加属性isDynamicField = true:2.在导出的方法中调用API时加参数new BlackList(list):3.导出效果: 三、导入1.要导入的文档:2.承接数据的java类3.调用导入API(这里在main方法测试就可以):4.执行结果:5.导入操作的保护机制: 四、总结

直奔主题,源码下载地址 在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 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 list = null; //从前端获取要导出动态属性的哪些 //List 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 list = null; //从前端获取要导出动态属性的哪些 //List 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() { @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 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 - ==> 开始执行导入操作... 执行每列的回调,当前是第5行1列,得到值为:1.0 执行每列的回调,当前是第5行2列,得到值为:小明 执行每列的回调,当前是第5行3列,得到值为:18.0 执行每列的回调,当前是第5行4列,得到值为:男 执行每列的回调,当前是第5行5列,得到值为:2021-12-10 10:12:25 执行每列的回调,当前是第5行6列,得到值为:新增小明 执行每列的回调,当前是第5行7列,得到值为:啊啊啊 执行每行的回调,当前是第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'} 执行每列的回调,当前是第6行1列,得到值为:2.0 执行每列的回调,当前是第6行2列,得到值为:小红 执行每列的回调,当前是第6行3列,得到值为:17.0 执行每列的回调,当前是第6行4列,得到值为:女 执行每列的回调,当前是第6行5列,得到值为:2021-12-10 10:12:25 执行每列的回调,当前是第6行6列,得到值为:新增小红 执行每列的回调,当前是第6行7列,得到值为:null 执行每列的回调,当前是第6行8列,得到值为:哦哦哦 执行每行的回调,当前是第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='哦哦哦'} 执行每列的回调,当前是第7行1列,得到值为:3.0 执行每列的回调,当前是第7行2列,得到值为:小刚 执行每列的回调,当前是第7行3列,得到值为:19.0 执行每列的回调,当前是第7行4列,得到值为:男 执行每列的回调,当前是第7行5列,得到值为:2021-12-10 10:12:25 执行每列的回调,当前是第7行6列,得到值为:新增小刚 执行每列的回调,当前是第7行7列,得到值为:呃呃呃 执行每行的回调,当前是第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'} 执行每列的回调,当前是第8行1列,得到值为:4.0 执行每列的回调,当前是第8行2列,得到值为:小强 执行每列的回调,当前是第8行3列,得到值为:20.0 执行每列的回调,当前是第8行4列,得到值为:男 执行每列的回调,当前是第8行5列,得到值为:2021-12-10 10:12:25 执行每列的回调,当前是第8行6列,得到值为:新增小强 执行每列的回调,当前是第8行7列,得到值为:null 执行每列的回调,当前是第8行8列,得到值为:嘤嘤嘤 执行每行的回调,当前是第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 哦!对了,我源码里面几乎每一步都有详细的注释,有兴趣的同学可以研究研究。可以多提提意见。


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3