以Excel格式导出查询出的数据是日常开发中常用到的功能。本人通过阅读若依开源项目源码,总结记录了若依是如何实现该功能的。
以Excel格式导出查询出的数据运用到了自定义注解、反射等基础知识,以及POI。具体步骤如下:
一、导入POI的jar包
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.0.0</version>
</dependency>
或
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.0.0</version>
</dependency>
二、POI的使用
POI的基本使用包括:创建工作薄、创建工作表、创建行、创建列、填充数据、设置样式
- 创建工作薄
SXSSFWorkbook wb = new SXSSFWorkbook(100);//100指的是最多可以容纳100个数据
- 创建工作表
Sheet sheet = wb.createSheet("sheetName");//参数是工作表名称
//也可先创建工作表,再设置名称
Sheet sheet = wb.createSheet();
Sheet sheet = wb.setSheetName(1, "sheetName");//第一个工作表的名字
- 创建行
Row row = sheet.createRow(0);//参数是第几行,这里是第0行,从第0行开始
- 创建列
Cell cell = row.createCell(0);//参数是第几列,这里是第0列,从第0列开始
- 填充数据
cell.setCellValue("name"); //参数是要填充的数据
- 设置样式
//创建样式
CellStyle style = wb.createCellStyle();
//1设置对齐方式
style.setAlignment(HorizontalAlignment.CENTER);//水平居中对齐
style.setVerticalAlignment(VerticalAlignment.CENTER);//垂直居中对齐
//2单元格边框样式
style.setBorderLeft(BorderStyle.THIN);//细线
style.setLeftBorderColor(IndexedColors.BLUE.getIndex());//蓝色
style.setBorderRight(BorderStyle.THIN);//破折号-点
style.setLeftBorderColor(IndexedColors.BLUE.getIndex());
style.setBorderTop(BorderStyle.THIN);//
style.setTopBorderColor(IndexedColors.BLUE.getIndex());
style.setBorderBottom(BorderStyle.THIN);//虚线
style.setBottomBorderColor(IndexedColors.BLUE.getIndex());
//3设置数据字体大小
Font dataFont = wb.createFont();
dataFont.setFontHeightInPoints((short) 10);
dataFont.setColor(IndexedColors.BLACK.getIndex());//设置字体颜色
dataFont.setFontName("Arial");//设置字体
style.setFont(dataFont);
//添加属性
cell.setCellStyle(style);
三、编写自定义注解Excel
在导出数据的时候,如果我们想导出自己想要的数据,注解就起了关键作用。我们可以在实体类中将自己想要导出的数据的字段上加上此注解,然后再利用反射获取这些加了注解的字段及其值,将其填入excel表格中即可。如下面的的例子所示
/**
* Excel注解
*/
@Target({ElementType.FIELD}) //只作用在字段上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Excel {
/**
* excel表格列头名字
* @return
*/
String name() default "";
/**
* 文字后缀 如90 -> 90%
* @return
*/
String suffix() default "";
/**
* excel表每列的高度 默认为 14 单位为字符
* @return
*/
double height() default 14;
/**
* excel表每列的宽度 默认为 16 单位为字符
* @return
*/
double weight() default 16;
/**
* 填入表格中的数据类型 0 String, 1 Number, 2 IMAGE
* @return
*/
ColumnType columnType() default ColumnType.STRING;
/**
* 枚举数据类型
*/
public enum ColumnType{
NUMBER(0),
STRING(1),
IMAGE(2);
private final int value;
ColumnType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
}
例子中注解的属性可以根据自己的需求添加。就算注解中一个属性都没有,照样可以将所想要的属性填充到Excel表格中,只是某些某些样式及类型转换方面比较麻烦。
如String name() default "";
这是设置列表头的名称,像实体类中“姓名”,“年龄”。。。都可以通过注解设置,在后面用反射获取即可。
四、使用注解
使用注解,在实体类的字段上加上即可,如下
@Excel(name = "id", columnType = Excel.ColumnType.NUMBER)
private Integer id; //ID
@Excel(name = "姓名")
private String name; //姓名
@Excel(name = "性别")
private String gender; //性别
@Excel(name = "年龄")
private Integer age; //年龄
@Excel(name = "地址")
private String address; //地址
private String qq; //qq
五、编写Excel工具类
1、编写初始化函数
这个函数的作用是利用反射获取实体类中加了Excel注解的字段和该注解,并保存下来。创建一个新的工作薄
public void init(List<T> dataList, String sheetName) {
if (dataList == null) {
dataList = new ArrayList<>();
}
this.dataList = dataList;
this.sheetName = sheetName;
//获取所有字段,表列头名称
createField();
//创建一个工作薄
createWorkBook();
}
/**
* 利用反射,获取所有字段,表列头名称
*/
public void createField() {
this.fields = new ArrayList<>();
Field[] tempFields = clazz.getDeclaredFields();//获取所有字段,无论权限
//遍历tempField,寻找带有Excel注解的字段,并将field和注解对象装进fields
for (Field field : tempFields) {
//判断该字段上是否有Excel注解
if (field.isAnnotationPresent(Excel.class)) {
Excel excel = field.getAnnotation(Excel.class);
this.fields.add(new Object[]{field, excel});
}
}
}
/**
* 创建一个工作薄
*/
public void createWorkBook() {
this.wb = new SXSSFWorkbook();
}
2、编写填充数据并导出函数
编写该函数主要分为以下几步:
- 创建工作表 sheet
createSheet
子函数 - 创建一行 row
- 将字段作为列表名(在Excel注解的name属性中填写的)填充进excel表格
createCell
子函数 - 将数据库中查询到的数据填进Excel表中
fillDataToExcel
子函数 - 将excel表格输出到指定位置
public void exportExcel() {
//1.创建sheet表
//1.1 根据数据的多少,判断需要创建几张sheet, 向上取整
int sheetNo = (int) Math.ceil(this.dataList.size() / sheetSize);
for (int i = 0; i <= sheetNo; i++) {
OutputStream os = null;
try {
//创建sheet表
createSheet(sheetNo, i);
//产生1行
Row row = sheet.createRow(0);
int column = 0;
//2.将字段作为列表名填进表格
for (Object[] field : this.fields) {
Excel tempExcel = (Excel) field[1];
//创建列,并将数据填充
createCell(row, tempExcel, column++);
}
//3.将数据填充到excel表格中
fillDataToExcel(i, row);
//4.输出
//获取文件名字
String fileName = encodingFileName(sheetName);
os =new FileOutputStream(getAbsoluteFile(fileName));
this.wb.write(os);
} catch (IOException e) {
e.printStackTrace();
}finally {
//释放资源
if (wb != null){
try {
wb.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/**
创建sheet表,并根据sheet表的数量为sheet表起名字
*/
public void createSheet(int sheetNo, int index) {
this.sheet = this.wb.createSheet();
//创建表格的样式,分别有,数据,列表头
createStyle(wb);
if (index == 0) {//第一个在工作表
this.wb.setSheetName(index, sheetName);
} else {
this.wb.setSheetName(index, sheetName + index);
}
}
/**
* 创建一列
*
* @param row 行
* @param excel
* @param column 列
*/
public void createCell(Row row, Excel excel, int column) {
Cell cell = row.createCell(column);
String name = excel.name();
//设置单元格样式
cell.setCellStyle(styleMap.get("head"));
//填充数据
cell.setCellValue(name);
}
/**
* 将数据填充至单元格
*
* @param index
* @param row
*/
public void fillDataToExcel(int index, Row row) {
//单元格开始位置
int startNo = index * sheetSize;
//单元格结束位置
int endNo = Math.min(startNo + sheetSize, dataList.size());
for (int j = startNo; j < endNo; j++) {//endNo - startNo 总行数
//创建行,没有列头,从0开始
row = this.sheet.createRow(j - startNo + 1);
//得到导出的对象
T vo = this.dataList.get(j);
int column = 0; //列号,从0开始
for (Object[] objects : this.fields) {
//设置实体私有属性可访问
Field field = (Field) objects[0];
Excel excel = (Excel) objects[1];
field.setAccessible(true);
//添加单元格,(列)
addCell(row, excel, vo, field, column++);
}
}
}
/**
* 添加单元格
*
* @param row
* @param vo
* @param field
* @param column
* @return
*/
public Cell addCell(Row row, Excel excel, T vo, Field field, int column) {
Cell cell = null;
try {
//创建单元格
cell = row.createCell(column);
//读取对象中的属性的值,如:
//field 为id 则 value为 id 的值 类型为Object类型
Object value = field.get(vo);
if (excel.columnType() == Excel.ColumnType.NUMBER) {
cell.setCellValue((Integer) value);
}else if (excel.columnType() == Excel.ColumnType.STRING) {
value = value == null ? value : value + excel.suffix();
cell.setCellValue((String) value);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
cell.setCellStyle(styleMap.get("data"));
return cell;
}
/**
* 创建表格的样式,分别有,数据,列表头
* 数据样式根据对齐方式,分别又分3种样式
*
* @param wb
*/
public void createStyle(SXSSFWorkbook wb) {
this.styleMap = new HashMap<>();
CellStyle style = wb.createCellStyle();
//1.设置数据格式
//1.1设置对齐方式
style.setAlignment(HorizontalAlignment.CENTER);//水平居中对齐
style.setVerticalAlignment(VerticalAlignment.CENTER);//垂直居中对齐
//1.2单元格边框样式
style.setBorderLeft(BorderStyle.THIN);//细线
style.setLeftBorderColor(IndexedColors.BLUE.getIndex());//蓝色
style.setBorderRight(BorderStyle.THIN);//破折号-点
style.setLeftBorderColor(IndexedColors.BLUE.getIndex());
style.setBorderTop(BorderStyle.THIN);//
style.setTopBorderColor(IndexedColors.BLUE.getIndex());
style.setBorderBottom(BorderStyle.THIN);//虚线
style.setBottomBorderColor(IndexedColors.BLUE.getIndex());
//1.3设置数据字体大小
Font dataFont = wb.createFont();
dataFont.setFontHeightInPoints((short) 10);
dataFont.setColor(IndexedColors.BLACK.getIndex());//设置字体颜色
dataFont.setFontName("Arial");//设置字体
style.setFont(dataFont);
styleMap.put("data", style);
//2.设置列表头样式
style = wb.createCellStyle();
style.cloneStyleFrom(styleMap.get("data"));
//2.1设置单元格背景颜色
style.setFillBackgroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
//2.2设置数据字体
Font headFont = wb.createFont();
headFont.setColor(IndexedColors.RED.getIndex());
headFont.setBold(true);//加粗
headFont.setFontName("Arial");
headFont.setFontHeightInPoints((short) 10);//设置字体大小
style.setFont(headFont);
styleMap.put("head", style);
}
3、将excel表格输出到指定位置
//输出
//获取文件名字
String fileName = encodingFileName(sheetName);
//获取文件绝对路径,并写入文件流中
os =new FileOutputStream(getAbsoluteFile(fileName));
this.wb.write(os);
/**
* 编码文件名称,使其具有唯一性
* @param fileName
* @return
*/
String encodingFileName(String fileName){
fileName = UUID.randomUUID().toString() + "_" +fileName + ".xlsx";
return fileName;
}
/**
获取文件绝对路径
fileName 文件名
*/
public String getAbsoluteFile(String fileName){
//从yml文件中获取绝对路径
String downloadPath = ExcelConfig.getDownLoad() + fileName;
File desc = new File(downloadPath);
//若文件路劲不存在,则创建
if (!desc.getParentFile().exists()){
desc.getParentFile().mkdirs();
}
return downloadPath;
}
注:ExcelConfig
此配置类可获取yml中文件中所设置的路径,使其可灵活配置路径
yml中的的配置
excel:
#文件路径 实例(Windows配置D:/excelUp/uploadPath)此处可填写服务器路径
profile: D:/excelUp/uploadPath
ExcelConfig配置类
@Component
@ConfigurationProperties(prefix = "excel")
public class ExcelConfig {
//文件路径
private static String profile;
//ConfigurationProperties注解需要此set函数
public void setProfile(String profile) {
this.profile = profile;
}
public static String getProfile() {
return profile;
}
public static String getDownLoad(){
return getProfile() + "/download/";
}
}