文章目录
直奔主题,源码下载地址
在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 - ==> 开始执行导入操作...
执行每列的回调,当前是第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
哦!对了,我源码里面几乎每一步都有详细的注释,有兴趣的同学可以研究研究。可以多提提意见。