以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/";
}
}