本文主要是介紹springboot + poi實現(xiàn)基本的excel文件導入導出,包含數(shù)據(jù)導出導入時數(shù)據(jù)的其他需求校驗,導出含有批注信息、導出含有圖片信息、導出含有圖表信息等的介紹等等,主要是一個demo盡可能簡單明了的來介紹相關功能即可。有什么問題可以在留言哦!并在文章末尾附上demo源碼下載!
一、前言
POI(Poor Obfuscation Implementation)是一個用于操作Microsoft Office格式文件的Java庫。它提供了對Excel、Word和PowerPoint文件的讀寫功能,POI是一個功能強大的Java庫,可以幫助開發(fā)人員在Java應用程序中處理Excel文件。無論是創(chuàng)建、編輯、讀取還是導出數(shù)據(jù),POI都提供了豐富的功能和靈活的接口。
單純做數(shù)據(jù)的導出導入的話用easyExcel是比較方便,如果對數(shù)據(jù)導出有特殊要求的話,建議還是使用poi來按照需求進行設置,比如數(shù)據(jù)導入時數(shù)據(jù)校驗,導入時數(shù)據(jù)的校驗和單元格樣式設置,導出含有批注信息、導出含有圖片信息、導出含有圖表信息等的介紹等等,這種情況還是poi的api用起比較舒服;注意如果不同版本的poi可能部分api是不兼容的,注意版本信息
?實體類對應的excel表數(shù)據(jù)
?二、導入依賴和創(chuàng)建自定義注解以及實體類
1、導入依賴
注意版本信息,不同的poi版本信息是不一樣的;
經(jīng)常在使用poi相關功能的時候,會導入一些其他模塊依賴來支撐;其中場景的依賴如下:
poi模塊:提供了對Microsoft Office格式文件的基本操作,如讀取、寫入和修改Excel、Word和PowerPoint文件。
poi-scratchpad模塊:提供了對一些較新的Microsoft Office格式文件的支持,如Excel 2007及以上版本的xlsx文件。
poi-ooxml模塊:提供了對Microsoft Office Open XML格式文件的支持,如xlsx、docx和pptx文件。
poi-ooxml-schemas模塊:提供了對Microsoft Office Open XML格式文件的底層支持,包含了所有的XML Schema定義。
poi-excelant模塊:提供了對Excel宏的支持,可以執(zhí)行和操作Excel宏。
這些模塊之間存在依賴關系,具體如下:
poi-scratchpad依賴于poi模塊,擴展了poi模塊的功能,使其能夠處理較新的Excel格式文件。
poi-ooxml依賴于poi模塊,提供了對Microsoft Office Open XML格式文件的支持。
poi-ooxml-schemas依賴于poi-ooxml模塊,提供了對Microsoft Office Open XML格式文件的底層支持。
poi-excelant依賴于poi模塊,提供了對Excel宏的支持。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>
</dependencies>
2、自定義注解
?3、創(chuàng)建實體類
package com.jdh.poi.excel.pojo;
import com.jdh.poi.excel.anno.PoiExcel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* @ClassName: Admin
* @Author: jdh
* @CreateTime: 2022-08-03
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Admin {
@PoiExcel(cellIndex = 0, cellName = "編號")
private Integer id;
@PoiExcel(cellIndex = 1, cellName = "名稱")
private String name;
@PoiExcel(cellIndex = 2, cellName = "區(qū)號")
private String zone;
@PoiExcel(cellIndex = 3, cellName = "能量值")
private Double energy;
@PoiExcel(cellIndex = 4, cellName = "創(chuàng)建時間")
private Date createTime;
@PoiExcel(cellIndex = 5, cellName = "備注")
private String remark;
}
三、excel導出導入的功能基本實現(xiàn)
其中包含數(shù)據(jù)導入時數(shù)據(jù)校驗,導入時數(shù)據(jù)的校驗和單元格樣式設置,導出含有批注信息、導出含有圖片信息、導出含有圖表信息等的介紹等等
1、獲取excel的基本數(shù)據(jù)信息
/**
* 獲取excel的基本數(shù)據(jù)信息,如sheet、行、列等
*
* @return
* @throws Exception
*/
@Test
public void excelReadBasicInfo() throws Exception {
FileInputStream inputStream = new FileInputStream(path + "admin_basic.xlsx");
//下面就用到poi的api了
Workbook workbook = WorkbookFactory.create(inputStream);
// Workbook workbook = new XSSFWorkbook(inputStream);
int sheetCount = workbook.getNumberOfSheets();
System.out.println("Sheet數(shù)量:" + sheetCount);
for (int i = 0; i < sheetCount; i++) {
Sheet sheet = workbook.getSheetAt(i);
// 獲取行數(shù)
int rowCount = sheet.getLastRowNum() + 1;
//獲取表頭列數(shù)
Row row0 = sheet.getRow(0);
int firstCellNum = row0.getLastCellNum();
// 獲取表中最大列數(shù)
int columnMaxCount = 0;
for (int j = 0; j < rowCount; j++) {
Row row = sheet.getRow(j);
if (row != null) {
int currentColumnCount = row.getLastCellNum();
if (currentColumnCount > columnMaxCount) {
columnMaxCount = currentColumnCount;
}
}
}
//注意:若讀取行列數(shù),如果單元格設置了單元格格式或者寫入數(shù)據(jù)又給刪掉,那么這種情況會被認為時有效單元格,在統(tǒng)計行或列的時候會被統(tǒng)計出來,但是肉眼看則時空行或列
//在解析數(shù)據(jù)的時候也會去解析該單元格數(shù)據(jù),會解析出一個空數(shù)據(jù),所以在解析數(shù)據(jù)的時候就需要增加單元格數(shù)據(jù)類型校驗
System.out.println("Sheet名稱:" + sheet.getSheetName() + ";行數(shù):" + rowCount + ";表頭列數(shù):" + firstCellNum + ";最大列數(shù):" + columnMaxCount);
}
System.out.println();
workbook.close();
inputStream.close();
}
2、直接讀取excel中的數(shù)據(jù)
/**
* 直接讀取excel中的數(shù)據(jù)
*
* @return
* @throws FileNotFoundException
*/
@Test
public void excelRead() throws FileNotFoundException {
File file = new File(path + "admin_basic.xlsx");
String fileType = file.getName().substring(file.getName().lastIndexOf(".") + 1);
FileInputStream inputStream = null;
Workbook wb = null;
try {
inputStream = new FileInputStream(file);
//這里可以不用判斷文件類型,直接使用WorkbookFactory來創(chuàng)建一個Workbook
//Workbook wb = WorkbookFactory.create(inputStream);
//判斷是什么格式
if (fileType.equals("xlsx")) {
wb = new XSSFWorkbook(inputStream);
} else if (fileType.equals("xls")) {
wb = new HSSFWorkbook(inputStream);
}
Map<Integer, List<List<Object>>> allExcelData = new HashMap<>();
//獲取當前excel文檔的sheet頁數(shù)
assert wb != null;
int sheetCount = wb.getNumberOfSheets();//sheet頁數(shù)
//讀取sheet的數(shù)據(jù)
for (int i = 0; i < sheetCount; i++) {
List<List<Object>> allSheetData = new ArrayList<>();//存儲當前sheet的數(shù)據(jù)
//獲取每一個sheet
Sheet sheet = wb.getSheetAt(i);
//每一個sheet的表名
String sheetName = sheet.getSheetName();
//獲取每一頁sheet的行數(shù)(包括標題行);若有的行單元格為設置了格式等操作但無數(shù)據(jù),也會被計算的
int rows = sheet.getPhysicalNumberOfRows();
//讀取每個sheet的行數(shù)據(jù)
for (int x = 0; x < rows; x++) {
ArrayList<Object> rowData = new ArrayList<>();//存儲當前行的數(shù)據(jù)
//獲取每一行數(shù)據(jù)
Row row = sheet.getRow(x);
//獲取最右邊有數(shù)據(jù)列的列數(shù),若有的列單元格為設置了格式等操作但無數(shù)據(jù),也會被計算的
int cellNum = row.getLastCellNum();
//獲取對應有數(shù)據(jù)的有效列數(shù),若有的列單元格為設置了格式等操作但無數(shù)據(jù),也會被計算的
int realCellNum = row.getPhysicalNumberOfCells();
//讀取每一行的每一個列的單元格數(shù)據(jù)
for (int y = 0; y < cellNum; y++) {
Cell cell = row.getCell(y);
Object value = getPoiExcelCellValueByType(cell);
rowData.add(value);//添加當前單元格數(shù)據(jù)
}
System.out.println("表:" + sheetName + ";行:" + x + ";最大列數(shù):" + cellNum + ";有效列數(shù):" + realCellNum);
allSheetData.add(rowData);//添加當前行數(shù)據(jù)
}
allExcelData.put(i, allSheetData);//添加當前sheet數(shù)據(jù)
}
System.out.println();
//輸出
for (int i = 0; i < sheetCount; i++) {
List<List<Object>> sheetData = allExcelData.get(i);
sheetData.forEach(r -> {
r.forEach(c -> System.out.print(c + " "));
System.out.println();
});
System.out.println("=========");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//關流操作(別忘了)
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意:讀取excel數(shù)據(jù)的時候,需要對當前單元格數(shù)據(jù)做數(shù)據(jù)校驗和轉(zhuǎn)換
/**
* 解析excel單元格數(shù)據(jù)類型和數(shù)據(jù)
* CellType._NONE 未知類型,僅限內(nèi)部使用
* CellType.NUMERIC 數(shù)值型-整數(shù)、小數(shù)、日期 0
* CellType.STRING 字符串型 1
* CellType.FORMULA 公式型 2
* CellType.BLANK 空單元格-沒值,但是有單元格樣式 3
* CellType.BOOLEAN 布爾型 4
* CellType.ERROR 錯誤單元格 5
*
* @param cell
* @return
*/
public static Object getPoiExcelCellValueByType(Cell cell) {
if (Objects.isNull(cell)) return null;
Object val;
CellType cellTypeEnum = cell.getCellTypeEnum();
if (cellTypeEnum == CellType.BLANK) {
val = "";
} else if (cellTypeEnum == CellType.NUMERIC) {
if (HSSFDateUtil.isCellDateFormatted(cell)) { //date日期格式
val = HSSFDateUtil.getJavaDate(cell.getNumericCellValue());
} else { // 純數(shù)字 整數(shù)/浮點數(shù)
//整數(shù)
if (Math.floor(cell.getNumericCellValue()) == cell.getNumericCellValue()) {
val = new DecimalFormat("0").format(cell.getNumericCellValue());
} else {//小數(shù)
val = new DecimalFormat("0.00").format(cell.getNumericCellValue());
}
}
} else if (cellTypeEnum == CellType.STRING) {
val = cell.getStringCellValue();
} else if (cellTypeEnum == CellType.FORMULA) {
val = cell.getCellFormula();
} else if (cellTypeEnum == CellType.BOOLEAN) {
val = cell.getBooleanCellValue();
} else if (cellTypeEnum == CellType.ERROR) {
val = cell.getErrorCellValue();
} else if (cellTypeEnum == CellType._NONE) {
val = null;
} else {
val = null;
}
return val;
}
3、將已有的數(shù)據(jù)導出為excel文件,并設置相關單元格格式
/**
* 將已有的數(shù)據(jù)導出為excel文件,并設置相關單元格格式
*
* @return
* @throws FileNotFoundException
*/
@Test
public void excelWrite() throws FileNotFoundException {
String fileName = "admin數(shù)據(jù)導出_PoiExcel.xlsx";
String sheetName = "Sheet";
//獲取一個文件輸出流
FileOutputStream outputStream = new FileOutputStream(path + fileName);
//構建workbook對象
XSSFWorkbook workbook = new XSSFWorkbook();
XSSFCellStyle style = workbook.createCellStyle();//單元格樣式
// style.setFillForegroundColor((short) 10);// 設置背景色
// style.setFillBackgroundColor((short) 10);//設置背景填充色
// style.setFillPattern(FillPatternType.BRICKS);//設置背景色規(guī)則
style.setBorderBottom(BorderStyle.DASHED); //下邊框
style.setBorderLeft(BorderStyle.DASH_DOT_DOT);//左邊框
//字體樣式是需要設置在cell列單元格上才生效
Font font = workbook.createFont();//字體樣式
font.setBold(false);//字體是否加粗
font.setFontName("宋體");//字體
font.setFontHeightInPoints((short) 12);//設置字體大小
font.setFontHeight((short) 300);//字體高度
font.setColor((short) 20);//字體顏色
// font.setColor(IndexedColors.BLACK.getIndex());
style.setFont(font);
//文本對齊方式,針對單元格有效
style.setVerticalAlignment(VerticalAlignment.CENTER);// 垂直
style.setAlignment(HorizontalAlignment.CENTER);// 水平居中
//創(chuàng)建sheet表
XSSFSheet sheet = workbook.createSheet("sheet1");
//下面創(chuàng)建表頭數(shù)據(jù),也可以自定義一個注解,通過反射來獲取類字段上的注解數(shù)據(jù),然后通過注解映射來完成字段生成表頭數(shù)據(jù)
Row headRow = sheet.createRow(0);//創(chuàng)建一行數(shù)據(jù)
//設置行高
// headRow.setHeight((short) 600);//以像素的1/20
headRow.setHeightInPoints(50);//這個是以像素點為基礎
//將表頭單元格數(shù)據(jù)樣式設置為上面的樣式
headRow.setRowStyle(style);
//合并單元格
CellRangeAddress cellRangeAddress = new CellRangeAddress(0, 0, 8, 11);
sheet.addMergedRegion(cellRangeAddress);
//設置列寬,但是這種直接設置會把其他列的寬度擠掉,因此在創(chuàng)建列的時候設置最好
sheet.setColumnWidth(1, 200);
Cell cell0 = headRow.createCell(0);//在第一行上創(chuàng)建一單元格
cell0.setCellValue("編號");//賦值
cell0.setCellType(CellType.STRING);//設置該單元格格式
cell0.setCellStyle(style);
Cell cell1 = headRow.createCell(1);
cell1.setCellValue("名稱");
cell1.setCellType(CellType.STRING);
Cell cell2 = headRow.createCell(2);
cell2.setCellValue("區(qū)號");
cell2.setCellType(CellType.STRING);
Cell cell3 = headRow.createCell(3);
cell3.setCellValue("能量值");
cell3.setCellType(CellType.STRING);
Cell cell4 = headRow.createCell(4);
cell4.setCellValue("時間");
cell4.setCellType(CellType.STRING);
Cell cell5 = headRow.createCell(5);
cell5.setCellValue("備注");
cell5.setCellType(CellType.STRING);
//用于解決時間轉(zhuǎn)換為double格式設置
CellStyle dateCellStyle = workbook.createCellStyle();
CreationHelper creationHelper = workbook.getCreationHelper();
dateCellStyle.setDataFormat(creationHelper.createDataFormat().getFormat("yyyy-MM-dd HH:mm:ss"));
for (int i = 0; i < data.size(); i++) {
Row row = sheet.createRow(i + 1);//創(chuàng)建正文行數(shù)據(jù)
//設置單元格格式;這一步可以通過一個類字段類型和excel類型一個map映射;這樣可以通過反射需要導出的類上字段的類型來字段確認excel單元格類型
row.createCell(0).setCellType(CellType.NUMERIC);
row.createCell(1).setCellType(CellType.STRING);
row.createCell(2).setCellType(CellType.STRING);
row.createCell(3).setCellType(CellType.NUMERIC);
row.createCell(4).setCellType(CellType.NUMERIC);
row.createCell(5).setCellType(CellType.STRING);
//設置數(shù)據(jù)
row.getCell(0).setCellValue(data.get(i).getId());
row.getCell(1).setCellValue(data.get(i).getName());
row.getCell(2).setCellValue(data.get(i).getZone());
row.getCell(3).setCellValue(data.get(i).getEnergy());
row.getCell(4).setCellStyle(dateCellStyle);
row.getCell(4).setCellValue(data.get(i).getCreateTime());
row.getCell(5).setCellValue(data.get(i).getRemark());
}
try {
workbook.write(outputStream);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
workbook.close();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4、導出的excel添加批注信息
/**
* 導出的excel添加批注信息
* <p>
* XSSFClientAnchor是Apache POI庫中用于在Excel中插入圖像的類。它用于指定圖像的位置和大小
* XSSFClientAnchor的構造函數(shù)具有以下參數(shù)設置和意義:
* 參數(shù)dx1: 圖像或形狀左上角相對于單元格左上角的X偏移量,以EMU(英制單位)為單位
* 參數(shù)dy1: 圖像或形狀左上角相對于單元格左上角的Y偏移量,以EMU為單位。
* 參數(shù)dx2: 圖像或形狀右下角相對于單元格左上角的X偏移量,以EMU為單位。
* 參數(shù)dy2: 圖像或形狀右下角相對于單元格左上角的Y偏移量,以EMU為單位。
* 參數(shù)col1: 單元格的起始列索引。表示圖像所在單元格的起始列索引
* 參數(shù)row1: 單元格的起始行索引。表示圖像所在單元格的起始行索引
* 參數(shù)col2: 單元格的結束列索引。表示圖像所在單元格的結束列索引
* 參數(shù)row2: 單元格的結束行索引。表示圖像所在單元格的結束行索引。
* 注意: dx1、dy1、dx2、dy2的范圍為0-1024,col1、col2、row1、row2的范圍為0-65535.
* 使用XSSFClientAnchor可以將圖像精確地定位到單元格上,并指定圖像在單元格內(nèi)的大小
*
* @throws FileNotFoundException
*/
@Test
public void excelPostWrite() throws FileNotFoundException {
String fileName = "admin數(shù)據(jù)批注模板導出_PoiExcel.xlsx";
String sheetName = "Sheet";
//獲取一個文件輸出流
FileOutputStream outputStream = new FileOutputStream(path + fileName);
//構建workbook對象
XSSFWorkbook workbook = new XSSFWorkbook();
XSSFCellStyle cellHeadStyle = workbook.createCellStyle();//單元格樣式
cellHeadStyle.setVerticalAlignment(VerticalAlignment.CENTER);// 垂直
cellHeadStyle.setAlignment(HorizontalAlignment.CENTER);// 水平
Font font = workbook.createFont();//字體樣式
font.setBold(true);//字體是否加粗
font.setFontName("宋體");//字體
font.setFontHeightInPoints((short) 10);//設置字體大小
font.setFontHeight((short) 250);//字體高度
font.setColor((short) 20);//字體顏色
cellHeadStyle.setFont(font);
//創(chuàng)建sheet表
XSSFSheet sheet = workbook.createSheet(sheetName);
//下面創(chuàng)建表頭數(shù)據(jù),也可以自定義一個注解,通過反射來獲取類字段上的注解數(shù)據(jù),然后通過注解映射來完成字段生成表頭數(shù)據(jù)
Row headRow = sheet.createRow(0);//創(chuàng)建一行數(shù)據(jù)
CreationHelper creationHelper = workbook.getCreationHelper();//用于創(chuàng)建各種對象,如ClientAnchor和Comment。
Drawing<?> drawing = sheet.createDrawingPatriarch();//用于在工作表上創(chuàng)建繪圖對象
Cell cell0 = headRow.createCell(0);//在第一行上創(chuàng)建一單元格
cell0.setCellValue("編號");//賦值
cell0.setCellType(CellType.STRING);//設置該單元格格式
cell0.setCellStyle(cellHeadStyle);
ClientAnchor anchor0 = creationHelper.createClientAnchor();//用于指定批注的位置
Comment comment0 = drawing.createCellComment(new XSSFClientAnchor(0, 0, 0, 0, 0, 0, 3, 3));//用于創(chuàng)建批注對象
comment0.setString(creationHelper.createRichTextString("這是數(shù)據(jù)編號,根據(jù)排列順序而定,由系統(tǒng)生成"));//用于創(chuàng)建富文本字符串,可以設置批注的內(nèi)容
cell0.setCellComment(comment0);//將批注與單元格關聯(lián)
Cell cell1 = headRow.createCell(1);
cell1.setCellValue("名稱");
cell1.setCellType(CellType.STRING);
cell1.setCellStyle(cellHeadStyle);
Comment comment1 = drawing.createCellComment(new XSSFClientAnchor(0, 0, 0, 0, 1, 0, 4, 3));//用于創(chuàng)建批注對象
comment1.setString(creationHelper.createRichTextString("這是數(shù)據(jù)對象名稱,根據(jù)創(chuàng)建數(shù)據(jù)對象自定義,一經(jīng)生成無法修改"));
cell1.setCellComment(comment1);
Cell cell2 = headRow.createCell(2);
cell2.setCellValue("區(qū)號");
cell2.setCellType(CellType.STRING);
cell2.setCellStyle(cellHeadStyle);
Comment comment2 = drawing.createCellComment(new XSSFClientAnchor(0, 0, 0, 0, 2, 0, 5, 3));//用于創(chuàng)建批注對象
comment2.setString(creationHelper.createRichTextString("這是數(shù)據(jù)對象所在區(qū)號,生成數(shù)據(jù)對象指定選中的區(qū)號,一經(jīng)生成無法修改"));
cell2.setCellComment(comment2);
Cell cell3 = headRow.createCell(3);
cell3.setCellValue("能量值");
cell3.setCellType(CellType.STRING);
cell3.setCellStyle(cellHeadStyle);
Comment comment3 = drawing.createCellComment(new XSSFClientAnchor(0, 0, 0, 0, 3, 0, 6, 3));//用于創(chuàng)建批注對象
comment3.setString(creationHelper.createRichTextString("這是數(shù)據(jù)對象所擁有的能量值,由數(shù)據(jù)對象使用過程中累積的"));
cell3.setCellComment(comment3);
Cell cell4 = headRow.createCell(4);
cell4.setCellValue("創(chuàng)建時間");
cell4.setCellType(CellType.STRING);
cell4.setCellStyle(cellHeadStyle);
Comment comment4 = drawing.createCellComment(new XSSFClientAnchor(0, 0, 0, 0, 4, 0, 7, 3));//用于創(chuàng)建批注對象
comment4.setString(creationHelper.createRichTextString("這是數(shù)據(jù)對象創(chuàng)建的時間,一經(jīng)生成無法修改"));
cell4.setCellComment(comment4);
Cell cell5 = headRow.createCell(5);
cell5.setCellValue("備注");
cell5.setCellType(CellType.STRING);
cell5.setCellStyle(cellHeadStyle);
Comment comment5 = drawing.createCellComment(new XSSFClientAnchor(0, 0, 0, 0, 5, 0, 8, 3));//用于創(chuàng)建批注對象
comment5.setString(creationHelper.createRichTextString("這是數(shù)據(jù)對象的備注"));
cell5.setCellComment(comment5);
//用于解決時間轉(zhuǎn)換為double格式設置
CellStyle dateCellStyle = workbook.createCellStyle();
dateCellStyle.setDataFormat(creationHelper.createDataFormat().getFormat("yyyy-MM-dd HH:mm:ss"));
for (int i = 0; i < data.size(); i++) {
Row row = sheet.createRow(i + 1);//創(chuàng)建正文行數(shù)據(jù)
//設置單元格格式;這一步可以通過一個類字段類型和excel類型一個map映射;這樣可以通過反射需要導出的類上字段的類型來字段確認excel單元格類型
row.createCell(0).setCellType(CellType.NUMERIC);
row.createCell(1).setCellType(CellType.STRING);
row.createCell(2).setCellType(CellType.STRING);
row.createCell(3).setCellType(CellType.NUMERIC);
row.createCell(4).setCellType(CellType.NUMERIC);
row.createCell(5).setCellType(CellType.STRING);
//設置數(shù)據(jù)
row.getCell(0).setCellValue(data.get(i).getId());
row.getCell(1).setCellValue(data.get(i).getName());
row.getCell(2).setCellValue(data.get(i).getZone());
row.getCell(3).setCellValue(data.get(i).getEnergy());
row.getCell(4).setCellStyle(dateCellStyle);
row.getCell(4).setCellValue(data.get(i).getCreateTime());
row.getCell(5).setCellValue(data.get(i).getRemark());
}
try {
workbook.write(outputStream);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
workbook.close();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
5、導出excel文檔,其中插入圖片以及圖表等數(shù)據(jù)
在導出圖表的時候,總是會遇到設置其他類型圖表類型,但導出來卻沒有變化,如果有大神知道,可以下放留言告訴我一下哦!
/**
* 導出excel文檔,其中插入圖片以及圖表等數(shù)據(jù)
*
* @throws IOException
*/
@Test
public void excelImgChartWrite() throws IOException {
String fileName = "數(shù)據(jù)圖片圖表模板導出_PoiExcel.xlsx";
String sheetName = "Sheet";
//獲取一個文件輸出流
FileOutputStream outputStream = new FileOutputStream(path + fileName);
// 創(chuàng)建工作簿
Workbook workbook = new XSSFWorkbook();
// 創(chuàng)建工作表
Sheet sheet = workbook.createSheet(sheetName);
for (int i = 0; i < 12; i++) {
Row row = sheet.createRow(i);//創(chuàng)建正文行數(shù)據(jù)
//設置數(shù)據(jù)
row.createCell(0).setCellValue("第" + (i + 1) + "月");
row.createCell(1).setCellValue(Math.random() * 100 + i);
row.createCell(2).setCellValue(Math.random() * 100 + i);
row.createCell(3).setCellValue(Math.random() * 100 + i);
row.createCell(4).setCellValue(Math.random() * 100 + i);
row.createCell(5).setCellValue(Math.random() * 100 + i);
}
// 讀取圖片文件
// InputStream inputStream = getClass().getResourceAsStream("/image.jpeg");
InputStream inputStream = new FileInputStream(path + "image.jpeg");
byte[] imageBytes = IOUtils.toByteArray(inputStream);
Drawing<?> drawing = sheet.createDrawingPatriarch();//用于在工作表上創(chuàng)建繪圖對象
// 將圖片插入單元格
int pictureIdx = sheet.getWorkbook().addPicture(imageBytes, Workbook.PICTURE_TYPE_JPEG);
CreationHelper helper = sheet.getWorkbook().getCreationHelper();
ClientAnchor anchor = helper.createClientAnchor();//用于指定圖片的位置
anchor.setCol1(9);//指定其實位置
anchor.setRow1(0);
anchor.setCol2(15);//指定結束位置
anchor.setRow2(12);
//這種寫法和上面的一樣
// ClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 9, 9, 15, 20);
Picture picture = drawing.createPicture(anchor, pictureIdx);//用于創(chuàng)建圖片對象
// picture.resize();//將圖像重置為嵌入圖像的尺寸,如果重設置大小的話,那么上面設置圖片位置只需要給的起始位置即可,圖片會更具自身大小存放再excel中
// 創(chuàng)建圖表,并設置圖表所在excel中區(qū)域位置
ClientAnchor anchor1 = drawing.createAnchor(0, 0, 0, 0, 0, 15, 10, 35);
XSSFChart chart = (XSSFChart) drawing.createChart(anchor1);//此版本需要轉(zhuǎn)換為XSSFChart才能設置圖表標題
chart.setTitleText("模擬圖表數(shù)據(jù)");//設置圖表標題
// 設置圖表類型為折線圖
// chart.getCTChart().addNewPlotArea().addNewLineChart();
// 設置圖表類型和數(shù)據(jù)源
ChartLegend legend = chart.getOrCreateLegend();
legend.setPosition(LegendPosition.TOP_RIGHT);//設置統(tǒng)計數(shù)據(jù)標識所有圖表位置
//采用的數(shù)據(jù)來源于哪些表格,主要實現(xiàn)圖表xy軸方向的坐標
ChartDataSource<Number> xs = DataSources.fromNumericCellRange(sheet, new CellRangeAddress(0, 11, 0, 0));
//下面分別是需要統(tǒng)計的數(shù)據(jù),例如ys1是統(tǒng)計0-11行第1列的數(shù)據(jù)
ChartDataSource<Number> ys1 = DataSources.fromNumericCellRange(sheet, new CellRangeAddress(0, 11, 1, 1));
ChartDataSource<Number> ys2 = DataSources.fromNumericCellRange(sheet, new CellRangeAddress(0, 11, 2, 2));
ChartDataSource<Number> ys3 = DataSources.fromNumericCellRange(sheet, new CellRangeAddress(0, 11, 3, 3));
//將統(tǒng)計的數(shù)據(jù)添加到圖表數(shù)據(jù)集中
// LineChartData data = chart.getChartDataFactory().createLineChartData();
// data.addSeries(xs, ys1);
// data.addSeries(xs, ys2);
// data.addSeries(xs, ys3);
ScatterChartData data = chart.getChartDataFactory().createScatterChartData();
data.addSerie(xs, ys1);
data.addSerie(xs, ys2);
data.addSerie(xs, ys3);
ChartAxis bottomAxis = chart.getChartAxisFactory().createCategoryAxis(AxisPosition.BOTTOM);
ValueAxis leftAxis = chart.getChartAxisFactory().createValueAxis(AxisPosition.LEFT);
leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
// 將圖表插入單元格
chart.plot(data, bottomAxis, leftAxis);
workbook.write(outputStream);
workbook.close();
outputStream.close();
}
四、對讀取excel數(shù)據(jù)方法封裝
1、封裝讀取的相關功能
結合自定義注解來實現(xiàn)讀取excel數(shù)據(jù)的封裝,其實思路和easyExcel的相關注解類似文章來源:http://www.zghlxwxcb.cn/news/detail-715892.html
package com.jdh.poi.excel.utils;
import com.jdh.poi.excel.anno.PoiExcel;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
import org.apache.poi.ss.formula.functions.T;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.text.DecimalFormat;
import java.util.*;
import java.util.stream.Collectors;
/**
* @ClassName: PoiExcelReadOperation
* @Author: jdh
* @CreateTime: 2022-08-09
* @Description: 指定類型讀取excel數(shù)據(jù)封裝
*/
@Slf4j
public class PoiExcelReadOperation {
/**
* 指定實體類讀取數(shù)據(jù)
* @param inputStream 文件輸入流
* @param clazz 實體類對象
* @param checkHead 是否讀取表頭
* @param <T>
* @return 返回對應的數(shù)據(jù)map;k為第幾張sheet,v為當前sheet中的數(shù)據(jù)
* @throws Exception
*/
public static <T> Map<Integer, List<T>> excelRead(InputStream inputStream, Class<T> clazz, Boolean checkHead) throws Exception {
Workbook workbook = WorkbookFactory.create(inputStream);
int sheetCount = workbook.getNumberOfSheets();
Field[] fields = clazz.getDeclaredFields();
List<Field> fieldList = Arrays.stream(fields).filter(f -> Objects.nonNull(f.getAnnotation(PoiExcel.class)))
.sorted(Comparator.comparing(el -> el.getAnnotation(PoiExcel.class).cellIndex()))
.collect(Collectors.toList());
fieldList.forEach(f -> f.setAccessible(true));
if(fieldList.size() == 0) return null;
Map<Integer, List<T>> excelData = new HashMap<>();
//讀取sheet的數(shù)據(jù)
for (int i = 0; i < sheetCount; i++) {
Sheet sheet = workbook.getSheetAt(i);//獲取每一個sheet
String sheetName = sheet.getSheetName();//每一個sheet的表名
//獲取每一頁sheet的行數(shù)(包括標題行);若有的行單元格為設置了格式等操作但無數(shù)據(jù),也會被計算的
int rows = sheet.getPhysicalNumberOfRows();
ArrayList<T> sheetData = new ArrayList<>();
//循環(huán)行數(shù)據(jù)
int index = checkHead ? 1 : 0;
for (int x = index; x < rows; x++) {
T t = clazz.newInstance();
//獲取每一行數(shù)據(jù)
Row row = sheet.getRow(x);
//獲取最右邊有數(shù)據(jù)列的列數(shù),若有的列單元格為設置了格式等操作但無數(shù)據(jù),也會被計算的
int cellNum = row.getLastCellNum();
//讀取每一行的每一個列的單元格數(shù)據(jù)
for (int y = 0; y < cellNum; y++) {
Cell cell = row.getCell(y);
//將對應單元格數(shù)據(jù)賦值給對應的實例字段
if (fieldList.size() != 0){
if (y <= fieldList.size()) {
fieldList.get(y).set(t, getPoiExcelCellValueByType(cell));
}
}else {
// //如果沒有指定PoiExcel注解,則采用默認順序進行賦值
// if (y <= fields.length) {
// //這里應該還需要判斷字段數(shù)據(jù)類型
// fields[y].set(t, (Object) getPoiExcelCellValueByType(cell));
// }
}
}
sheetData.add(t);
}
excelData.put(i,sheetData);
}
workbook.close();
return excelData;
}
/**
* CellType._NONE 未知類型,僅限內(nèi)部使用 -1
* CellType.NUMERIC 數(shù)值型-整數(shù)、小數(shù)、日期 0
* CellType.STRING 字符串型 1
* CellType.FORMULA 公式型 2
* CellType.BLANK 空單元格-沒值,但是有單元格樣式 3
* CellType.BOOLEAN 布爾型 4
* CellType.ERROR 錯誤單元格 5
*
* @param cell 當前單元格
* @return
*/
public static Object getPoiExcelCellValueByType(Cell cell) {
if (Objects.isNull(cell)) {
log.info("cell is null");
return null;
}
Object val;
CellType cellTypeEnum = cell.getCellTypeEnum();
if (cellTypeEnum == CellType.BLANK) {
val = "";
} else if (cellTypeEnum == CellType.NUMERIC) {
if (HSSFDateUtil.isCellDateFormatted(cell)) { //date日期格式
val = HSSFDateUtil.getJavaDate(cell.getNumericCellValue());
} else { // 純數(shù)字 整數(shù)/浮點數(shù)
//整數(shù)
if (Math.floor(cell.getNumericCellValue()) == cell.getNumericCellValue()) {
val = Integer.parseInt(new DecimalFormat("0").format(cell.getNumericCellValue()));
} else {//小數(shù)
val = Double.parseDouble(new DecimalFormat("0.00").format(cell.getNumericCellValue()));
}
}
} else if (cellTypeEnum == CellType.STRING) {
val = cell.getStringCellValue();
} else if (cellTypeEnum == CellType.FORMULA) {
val = cell.getCellFormula();
} else if (cellTypeEnum == CellType.BOOLEAN) {
val = cell.getBooleanCellValue();
} else if (cellTypeEnum == CellType.ERROR) {
val = cell.getErrorCellValue();
} else if (cellTypeEnum == CellType._NONE) {
val = null;
} else {
val = null;
}
return val;
}
}
2、對讀取數(shù)據(jù)封裝進行測試
package com.jdh.poi.excel.utils;
import com.jdh.poi.excel.pojo.Admin;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class PoiExcelReadOperationTest {
private static final String path = System.getProperty("user.dir") + "\\Doc\\";
@Test
void excelRead() {
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(path + "admin_basic.xlsx");
Map<Integer, List<Admin>> listMap = PoiExcelReadOperation.excelRead(inputStream, Admin.class, true);
assert listMap != null;
List<Admin> admins = listMap.get(0);
admins.forEach(System.out::println);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
assert inputStream != null;
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
五、對數(shù)據(jù)導出進行封裝
1、封裝數(shù)據(jù)導出相關功能
package com.jdh.poi.excel.utils;
import com.jdh.poi.excel.anno.PoiExcel;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;
/**
* @ClassName: PoiExcelReadOperation
* @Author: jdh
* @CreateTime: 2022-08-03
* @Description: poiExcel指定實體類導出數(shù)據(jù)或者模板
*/
public class PoiExcelWriteOperation {
/**
* 根據(jù)指定實體類,導出excel文檔數(shù)據(jù)
*
* @param outputStream 文件輸出流
* @param dataMap 需要導出的數(shù)據(jù),key為sheet名稱,v為需要導出的數(shù)據(jù),若傳null或者v為null則導含表頭的模板
* @param clazz 需要指定導出的實體類對象
* @throws Exception
*/
public static <T> void excelWrite(OutputStream outputStream, LinkedHashMap<String, List<T>> dataMap, Class<T> clazz) throws Exception {
//構建workbook對象
XSSFWorkbook workbook = new XSSFWorkbook();
XSSFCellStyle style = workbook.createCellStyle();//單元格樣式
style.setVerticalAlignment(VerticalAlignment.CENTER);// 垂直
style.setAlignment(HorizontalAlignment.CENTER);// 水平
XSSFCellStyle cellHeadStyle = workbook.createCellStyle();//單元格樣式
cellHeadStyle.setVerticalAlignment(VerticalAlignment.CENTER);// 垂直
cellHeadStyle.setAlignment(HorizontalAlignment.CENTER);// 水平
Font font = workbook.createFont();//字體樣式
font.setBold(true);//字體是否加粗
font.setFontName("宋體");//字體
font.setFontHeightInPoints((short) 10);//設置字體大小
font.setFontHeight((short) 250);//字體高度
font.setColor((short) 20);//字體顏色
style.setFont(font);
cellHeadStyle.setFont(font);
Field[] fields = clazz.getDeclaredFields();
List<Field> fieldList = Arrays.stream(fields).filter(f -> Objects.nonNull(f.getAnnotation(PoiExcel.class)))
.sorted(Comparator.comparing(el -> el.getAnnotation(PoiExcel.class).cellIndex()))
.collect(Collectors.toList());
fieldList.forEach(f -> f.setAccessible(true));
//若對應的實體類的字段屬性未指定PoiExcel注解則不認為導出
if (fieldList.size() == 0) return;
if (dataMap.size() == 0) {
dataMap.put("sheet", null);
}
//所有需要導出excel對應sheet的名稱
ArrayList<String> sheetNameList = new ArrayList<>(dataMap.keySet());
//循環(huán)當前需要創(chuàng)建的sheet
for (int i = 0; i < dataMap.size(); i++) {
//創(chuàng)建sheet表
XSSFSheet sheet = workbook.createSheet(sheetNameList.get(i));
//需要導出的數(shù)據(jù)集合
List<T> ts = dataMap.get(sheetNameList.get(i));
//如果對應的數(shù)據(jù)集合為空,則為模板導出模式
int rowCount = Objects.isNull(ts) ? 0 : ts.size();
Row headRow = sheet.createRow(0);//創(chuàng)建一行數(shù)據(jù)
//下面創(chuàng)建表頭數(shù)據(jù)
headRow.setHeightInPoints(35);//這個是以像素點為基礎
//添加表頭數(shù)據(jù)
for (int x = 0; x < fieldList.size(); x++) {
Field field = fieldList.get(x);
PoiExcel poiExcel = field.getAnnotation(PoiExcel.class);
Cell cell = headRow.createCell(x);
cell.setCellValue(poiExcel.cellName());
cell.setCellType(CellType.STRING);
cell.setCellStyle(cellHeadStyle);
}
//循環(huán)當前的行(不含表頭)
for (int r = 0; r < rowCount; r++) {
Row row = sheet.createRow(r + 1);
T t = ts.get(r);//某一行的數(shù)據(jù)
//循環(huán)列對每一個單元格賦值
for (int x = 0; x < fieldList.size(); x++) {
Field field = fieldList.get(x);
Cell cell = row.createCell(x);
//這里賦值的時候,最好做實體類字段與單元格類型的映射關系,并賦值指定類型
String value = field.get(t).toString();
cell.setCellType(CellType.STRING);
cell.setCellValue(value);
}
}
}
workbook.write(outputStream);
workbook.close();
}
}
2、對導出數(shù)據(jù)封裝進行測試
package com.jdh.poi.excel.utils;
import com.jdh.poi.excel.pojo.Admin;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class PoiExcelWriteOperationTest {
private static final String path = System.getProperty("user.dir") + "\\Doc\\";
//這個數(shù)據(jù)是模擬從數(shù)據(jù)庫查詢的數(shù)據(jù)
public static final List<Admin> data = new ArrayList<>(Arrays.asList(
new Admin(1, "admin001", "一區(qū)", 8500.05, new Date(), "qq一區(qū)管理員"),
new Admin(2, "admin002", "二區(qū)", 8700.05, new Date(), "qq二區(qū)管理員"),
new Admin(3, "admin003", "三區(qū)", 8800.05, new Date(), "qq三區(qū)管理員"),
new Admin(4, "admin004", "四區(qū)", 8900.05, new Date(), "qq四區(qū)管理員"),
new Admin(5, "admin005", "五區(qū)", 9500.05, new Date(), "qq五區(qū)管理員"),
new Admin(6, "admin006", "六區(qū)", 10000.05, new Date(), "qq六區(qū)管理員")
));
@Test
void excelWrite() {
//獲取一個文件輸出流
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(path + "admin模板數(shù)據(jù)導出_poiExcel.xlsx");
LinkedHashMap<String, List<Admin>> dataMap = new LinkedHashMap<>();
dataMap.put("sheet01",data);
dataMap.put("sheet02",data);
PoiExcelWriteOperation.excelWrite(outputStream,dataMap,Admin.class);
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
assert outputStream != null;
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
六、結尾
源碼gitee地址:文章來源地址http://www.zghlxwxcb.cn/news/detail-715892.html
EasyExcel和POI版本對應關系:
- EasyExcel 3.0.x: POI 3.17
- EasyExcel 3.1.x: POI 4.0.1
- EasyExcel 3.1.4: POI 4.1.2
poi常用操作說明如下:
Apache POI 是用Java編寫的免費開源的跨平臺的 Java API,Apache POI提供API給Java程式對Microsoft Office格式檔案讀和寫的功能。 POI為“Poor Obfuscation Implementation”的首字母縮寫,意為“簡潔版的模糊實現(xiàn)”。 一、常用文檔類型說明 HSSF : 讀寫 Microsoft Excel XLS 格式文檔 XSSF : 讀寫 Microsoft Excel OOXML XLSX 格式文檔 SXSSF : 讀寫 Microsoft Excel OOXML XLSX 格式文檔 HWPF : 讀寫 Microsoft Word DOC 格式文檔 HSLF : 讀寫 Microsoft PowerPoint 格式文檔 HDGF : 讀 Microsoft Visio 格式文檔 HPBF : 讀 Microsoft Publisher 格式文檔 HSMF : 讀 Microsoft Outlook 格式文檔 二、POI常用類說明 類名 => 說明: HSSFWorkbook => Excel的文檔對象 HSSFSheet => Excel的表單 HSSFRow => Excel的行 HSSFCell => Excel的格子單元 HSSFFont => Excel字體 HSSFDataFormat => 格子單元的日期格式 HSSFHeader => Excel文檔Sheet的頁眉 HSSFFooter => Excel文檔Sheet的頁腳 HSSFCellStyle => 格子單元樣式 HSSFDateUtil => 日期 HSSFPrintSetup => 打印 HSSFErrorConstants => 錯誤信息表 三、常用單元格數(shù)據(jù)類型說明 CellType => 單元格類型 => 對應的數(shù)值: CellType._NONE => 未知類型,僅限內(nèi)部使用 => -1 CellType.NUMERIC => 數(shù)值型-整數(shù)、小數(shù)、日期 => 0 CellType.STRING => 字符串型 => 1 CellType.FORMULA => 公式型 => 2 CellType.BLANK => 空單元格-沒值,但是有單元格樣式 => 3 CellType.BOOLEAN => 布爾型 => 4 CellType.ERROR => 錯誤單元格 => 5 Tips: 日期數(shù)據(jù)對應的單元格類型是CellType.NUMERIC,默認以浮點型數(shù)顯示,顯示為日期格式需要設置單元格樣式DataFormat, 字符型單元格內(nèi)容也可以為富文本RichTextString,可以對文本多部分設置字體Font
到了這里,關于poi實現(xiàn)excel文件導入導出(基本數(shù)據(jù)導出、含格式導出、含批注導出、含圖片圖表導出)——springboot的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!