本文主要是介绍springboot + poi实现基本的excel文件导入导出,包含数据导出导入时数据的其他需求校验,导出含有批注信息、导出含有图片信息、导出含有图表信息等的介绍等等,主要是一个demo尽可能简单明了的来介绍相关功能即可。有什么问题可以在留言哦!并在文章末尾附上demo源码下载!
一、前言
POI(Poor Obfuscation Implementation)是一个用于操作Microsoft Office格式文件的java库。它提供了对Excel、Word和PowerPoint文件的读写功能,POI是一个功能强大的Java库,可以帮助开发人员在Java应用程序中处理Excel文件。无论是创建、编辑、读取还是导出数据,POI都提供了丰富的功能和灵活的接口。
单纯做数据的导出导入的话用easyExcel是比较方便,如果对数据导出有特殊要求的话,建议还是使用poi来按照需求进行设置,比如数据导入时数据校验,导入时数据的校验和单元格样式设置,导出含有批注信息、导出含有图片信息、导出含有图表信息等的介绍等等,这种情况还是poi的api用起比较舒服;注意如果不同版本的poi可能部分api是不兼容的,注意版本信息
实体类对应的excel表数据
二、导入依赖和创建自定义注解以及实体类
1、导入依赖
注意版本信息,不同的poi版本信息是不一样的;
经常在使用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宏的支持,可以执行和操作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、创建实体类
package ***.jdh.poi.excel.pojo;
import ***.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 = "区号")
private String zone;
@PoiExcel(cellIndex = 3, cellName = "能量值")
private Double energy;
@PoiExcel(cellIndex = 4, cellName = "创建时间")
private Date createTime;
@PoiExcel(cellIndex = 5, cellName = "备注")
private String remark;
}
三、excel导出导入的功能基本实现
其中包含数据导入时数据校验,导入时数据的校验和单元格样式设置,导出含有批注信息、导出含有图片信息、导出含有图表信息等的介绍等等
1、获取excel的基本数据信息
/**
* 获取excel的基本数据信息,如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数量:" + sheetCount);
for (int i = 0; i < sheetCount; i++) {
Sheet sheet = workbook.getSheetAt(i);
// 获取行数
int rowCount = sheet.getLastRowNum() + 1;
//获取表头列数
Row row0 = sheet.getRow(0);
int firstCellNum = row0.getLastCellNum();
// 获取表中最大列数
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;
}
}
}
//注意:若读取行列数,如果单元格设置了单元格格式或者写入数据又给删掉,那么这种情况会被认为时有效单元格,在统计行或列的时候会被统计出来,但是肉眼看则时空行或列
//在解析数据的时候也会去解析该单元格数据,会解析出一个空数据,所以在解析数据的时候就需要增加单元格数据类型校验
System.out.println("Sheet名称:" + sheet.getSheetName() + ";行数:" + rowCount + ";表头列数:" + firstCellNum + ";最大列数:" + columnMaxCount);
}
System.out.println();
workbook.close();
inputStream.close();
}
2、直接读取excel中的数据
/**
* 直接读取excel中的数据
*
* @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来创建一个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页数
assert wb != null;
int sheetCount = wb.getNumberOfSheets();//sheet页数
//读取sheet的数据
for (int i = 0; i < sheetCount; i++) {
List<List<Object>> allSheetData = new ArrayList<>();//存储当前sheet的数据
//获取每一个sheet
Sheet sheet = wb.getSheetAt(i);
//每一个sheet的表名
String sheetName = sheet.getSheetName();
//获取每一页sheet的行数(包括标题行);若有的行单元格为设置了格式等操作但无数据,也会被计算的
int rows = sheet.getPhysicalNumberOfRows();
//读取每个sheet的行数据
for (int x = 0; x < rows; x++) {
ArrayList<Object> rowData = new ArrayList<>();//存储当前行的数据
//获取每一行数据
Row row = sheet.getRow(x);
//获取最右边有数据列的列数,若有的列单元格为设置了格式等操作但无数据,也会被计算的
int cellNum = row.getLastCellNum();
//获取对应有数据的有效列数,若有的列单元格为设置了格式等操作但无数据,也会被计算的
int realCellNum = row.getPhysicalNumberOfCells();
//读取每一行的每一个列的单元格数据
for (int y = 0; y < cellNum; y++) {
Cell cell = row.getCell(y);
Object value = getPoiExcelCellValueByType(cell);
rowData.add(value);//添加当前单元格数据
}
System.out.println("表:" + sheetName + ";行:" + x + ";最大列数:" + cellNum + ";有效列数:" + realCellNum);
allSheetData.add(rowData);//添加当前行数据
}
allExcelData.put(i, allSheetData);//添加当前sheet数据
}
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数据的时候,需要对当前单元格数据做数据校验和转换
/**
* 解析excel单元格数据类型和数据
* CellType._NONE 未知类型,仅限内部使用
* CellType.NUMERIC 数值型-整数、小数、日期 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.getNumeri***ellValue());
} else { // 纯数字 整数/浮点数
//整数
if (Math.floor(cell.getNumeri***ellValue()) == cell.getNumeri***ellValue()) {
val = new DecimalFormat("0").format(cell.getNumeri***ellValue());
} else {//小数
val = new DecimalFormat("0.00").format(cell.getNumeri***ellValue());
}
}
} 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、将已有的数据导出为excel文件,并设置相关单元格格式
/**
* 将已有的数据导出为excel文件,并设置相关单元格格式
*
* @return
* @throws FileNotFoundException
*/
@Test
public void excelWrite() throws FileNotFoundException {
String fileName = "admin数据导出_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);//设置背景色规则
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);// 水平居中
//创建sheet表
XSSFSheet sheet = workbook.createSheet("sheet1");
//下面创建表头数据,也可以自定义一个注解,通过反射来获取类字段上的注解数据,然后通过注解映射来完成字段生成表头数据
Row headRow = sheet.createRow(0);//创建一行数据
//设置行高
// headRow.setHeight((short) 600);//以像素的1/20
headRow.setHeightInPoints(50);//这个是以像素点为基础
//将表头单元格数据样式设置为上面的样式
headRow.setRowStyle(style);
//合并单元格
CellRangeAddress cellRangeAddress = new CellRangeAddress(0, 0, 8, 11);
sheet.addMergedRegion(cellRangeAddress);
//设置列宽,但是这种直接设置会把其他列的宽度挤掉,因此在创建列的时候设置最好
sheet.setColumnWidth(1, 200);
Cell cell0 = headRow.createCell(0);//在第一行上创建一单元格
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("区号");
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);
//用于解决时间转换为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);//创建正文行数据
//设置单元格格式;这一步可以通过一个类字段类型和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);
//设置数据
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的构造函数具有以下参数设置和意义:
* 参数dx1: 图像或形状左上角相对于单元格左上角的X偏移量,以EMU(英制单位)为单位
* 参数dy1: 图像或形状左上角相对于单元格左上角的Y偏移量,以EMU为单位。
* 参数dx2: 图像或形状右下角相对于单元格左上角的X偏移量,以EMU为单位。
* 参数dy2: 图像或形状右下角相对于单元格左上角的Y偏移量,以EMU为单位。
* 参数col1: 单元格的起始列索引。表示图像所在单元格的起始列索引
* 参数row1: 单元格的起始行索引。表示图像所在单元格的起始行索引
* 参数col2: 单元格的结束列索引。表示图像所在单元格的结束列索引
* 参数row2: 单元格的结束行索引。表示图像所在单元格的结束行索引。
* 注意: dx1、dy1、dx2、dy2的范围为0-1024,col1、col2、row1、row2的范围为0-65535.
* 使用XSSFClientAnchor可以将图像精确地定位到单元格上,并指定图像在单元格内的大小
*
* @throws FileNotFoundException
*/
@Test
public void excelPostWrite() throws FileNotFoundException {
String fileName = "admin数据批注模板导出_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);
//创建sheet表
XSSFSheet sheet = workbook.createSheet(sheetName);
//下面创建表头数据,也可以自定义一个注解,通过反射来获取类字段上的注解数据,然后通过注解映射来完成字段生成表头数据
Row headRow = sheet.createRow(0);//创建一行数据
CreationHelper creationHelper = workbook.getCreationHelper();//用于创建各种对象,如ClientAnchor和***ment。
Drawing<?> drawing = sheet.createDrawingPatriarch();//用于在工作表上创建绘图对象
Cell cell0 = headRow.createCell(0);//在第一行上创建一单元格
cell0.setCellValue("编号");//赋值
cell0.setCellType(CellType.STRING);//设置该单元格格式
cell0.setCellStyle(cellHeadStyle);
ClientAnchor anchor0 = creationHelper.createClientAnchor();//用于指定批注的位置
***ment ***ment0 = drawing.createCell***ment(new XSSFClientAnchor(0, 0, 0, 0, 0, 0, 3, 3));//用于创建批注对象
***ment0.setString(creationHelper.createRichTextString("这是数据编号,根据排列顺序而定,由系统生成"));//用于创建富文本字符串,可以设置批注的内容
cell0.setCell***ment(***ment0);//将批注与单元格关联
Cell cell1 = headRow.createCell(1);
cell1.setCellValue("名称");
cell1.setCellType(CellType.STRING);
cell1.setCellStyle(cellHeadStyle);
***ment ***ment1 = drawing.createCell***ment(new XSSFClientAnchor(0, 0, 0, 0, 1, 0, 4, 3));//用于创建批注对象
***ment1.setString(creationHelper.createRichTextString("这是数据对象名称,根据创建数据对象自定义,一经生成无法修改"));
cell1.setCell***ment(***ment1);
Cell cell2 = headRow.createCell(2);
cell2.setCellValue("区号");
cell2.setCellType(CellType.STRING);
cell2.setCellStyle(cellHeadStyle);
***ment ***ment2 = drawing.createCell***ment(new XSSFClientAnchor(0, 0, 0, 0, 2, 0, 5, 3));//用于创建批注对象
***ment2.setString(creationHelper.createRichTextString("这是数据对象所在区号,生成数据对象指定选中的区号,一经生成无法修改"));
cell2.setCell***ment(***ment2);
Cell cell3 = headRow.createCell(3);
cell3.setCellValue("能量值");
cell3.setCellType(CellType.STRING);
cell3.setCellStyle(cellHeadStyle);
***ment ***ment3 = drawing.createCell***ment(new XSSFClientAnchor(0, 0, 0, 0, 3, 0, 6, 3));//用于创建批注对象
***ment3.setString(creationHelper.createRichTextString("这是数据对象所拥有的能量值,由数据对象使用过程中累积的"));
cell3.setCell***ment(***ment3);
Cell cell4 = headRow.createCell(4);
cell4.setCellValue("创建时间");
cell4.setCellType(CellType.STRING);
cell4.setCellStyle(cellHeadStyle);
***ment ***ment4 = drawing.createCell***ment(new XSSFClientAnchor(0, 0, 0, 0, 4, 0, 7, 3));//用于创建批注对象
***ment4.setString(creationHelper.createRichTextString("这是数据对象创建的时间,一经生成无法修改"));
cell4.setCell***ment(***ment4);
Cell cell5 = headRow.createCell(5);
cell5.setCellValue("备注");
cell5.setCellType(CellType.STRING);
cell5.setCellStyle(cellHeadStyle);
***ment ***ment5 = drawing.createCell***ment(new XSSFClientAnchor(0, 0, 0, 0, 5, 0, 8, 3));//用于创建批注对象
***ment5.setString(creationHelper.createRichTextString("这是数据对象的备注"));
cell5.setCell***ment(***ment5);
//用于解决时间转换为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);//创建正文行数据
//设置单元格格式;这一步可以通过一个类字段类型和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);
//设置数据
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文档,其中插入图片以及图表等数据
在导出图表的时候,总是会遇到设置其他类型图表类型,但导出来却没有变化,如果有大神知道,可以下放留言告诉我一下哦!
/**
* 导出excel文档,其中插入图片以及图表等数据
*
* @throws IOException
*/
@Test
public void excelImgChartWrite() throws IOException {
String fileName = "数据图片图表模板导出_PoiExcel.xlsx";
String sheetName = "Sheet";
//获取一个文件输出流
FileOutputStream outputStream = new FileOutputStream(path + fileName);
// 创建工作簿
Workbook workbook = new XSSFWorkbook();
// 创建工作表
Sheet sheet = workbook.createSheet(sheetName);
for (int i = 0; i < 12; i++) {
Row row = sheet.createRow(i);//创建正文行数据
//设置数据
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();//用于在工作表上创建绘图对象
// 将图片插入单元格
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);//用于创建图片对象
// picture.resize();//将图像重置为嵌入图像的尺寸,如果重设置大小的话,那么上面设置图片位置只需要给的起始位置即可,图片会更具自身大小存放再excel中
// 创建图表,并设置图表所在excel中区域位置
ClientAnchor anchor1 = drawing.createAnchor(0, 0, 0, 0, 0, 15, 10, 35);
XSSFChart chart = (XSSFChart) drawing.createChart(anchor1);//此版本需要转换为XSSFChart才能设置图表标题
chart.setTitleText("模拟图表数据");//设置图表标题
// 设置图表类型为折线图
// chart.getCTChart().addNewPlotArea().addNewLineChart();
// 设置图表类型和数据源
ChartLegend legend = chart.getOrCreateLegend();
legend.setPosition(LegendPosition.TOP_RIGHT);//设置统计数据标识所有图表位置
//采用的数据来源于哪些表格,主要实现图表xy轴方向的坐标
ChartDataSource<Number> xs = DataSources.fromNumeri***ellRange(sheet, new CellRangeAddress(0, 11, 0, 0));
//下面分别是需要统计的数据,例如ys1是统计0-11行第1列的数据
ChartDataSource<Number> ys1 = DataSources.fromNumeri***ellRange(sheet, new CellRangeAddress(0, 11, 1, 1));
ChartDataSource<Number> ys2 = DataSources.fromNumeri***ellRange(sheet, new CellRangeAddress(0, 11, 2, 2));
ChartDataSource<Number> ys3 = DataSources.fromNumeri***ellRange(sheet, new CellRangeAddress(0, 11, 3, 3));
//将统计的数据添加到图表数据集中
// 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数据方法封装
1、封装读取的相关功能
结合自定义注解来实现读取excel数据的封装,其实思路和easyExcel的相关注解类似
package ***.jdh.poi.excel.utils;
import ***.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数据封装
*/
@Slf4j
public class PoiExcelReadOperation {
/**
* 指定实体类读取数据
* @param inputStream 文件输入流
* @param clazz 实体类对象
* @param checkHead 是否读取表头
* @param <T>
* @return 返回对应的数据map;k为第几张sheet,v为当前sheet中的数据
* @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(***parator.***paring(el -> el.getAnnotation(PoiExcel.class).cellIndex()))
.collect(Collectors.toList());
fieldList.forEach(f -> f.setA***essible(true));
if(fieldList.size() == 0) return null;
Map<Integer, List<T>> excelData = new HashMap<>();
//读取sheet的数据
for (int i = 0; i < sheetCount; i++) {
Sheet sheet = workbook.getSheetAt(i);//获取每一个sheet
String sheetName = sheet.getSheetName();//每一个sheet的表名
//获取每一页sheet的行数(包括标题行);若有的行单元格为设置了格式等操作但无数据,也会被计算的
int rows = sheet.getPhysicalNumberOfRows();
ArrayList<T> sheetData = new ArrayList<>();
//循环行数据
int index = checkHead ? 1 : 0;
for (int x = index; x < rows; x++) {
T t = clazz.newInstance();
//获取每一行数据
Row row = sheet.getRow(x);
//获取最右边有数据列的列数,若有的列单元格为设置了格式等操作但无数据,也会被计算的
int cellNum = row.getLastCellNum();
//读取每一行的每一个列的单元格数据
for (int y = 0; y < cellNum; y++) {
Cell cell = row.getCell(y);
//将对应单元格数据赋值给对应的实例字段
if (fieldList.size() != 0){
if (y <= fieldList.size()) {
fieldList.get(y).set(t, getPoiExcelCellValueByType(cell));
}
}else {
// //如果没有指定PoiExcel注解,则采用默认顺序进行赋值
// if (y <= fields.length) {
// //这里应该还需要判断字段数据类型
// fields[y].set(t, (Object) getPoiExcelCellValueByType(cell));
// }
}
}
sheetData.add(t);
}
excelData.put(i,sheetData);
}
workbook.close();
return excelData;
}
/**
* CellType._NONE 未知类型,仅限内部使用 -1
* CellType.NUMERIC 数值型-整数、小数、日期 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.getNumeri***ellValue());
} else { // 纯数字 整数/浮点数
//整数
if (Math.floor(cell.getNumeri***ellValue()) == cell.getNumeri***ellValue()) {
val = Integer.parseInt(new DecimalFormat("0").format(cell.getNumeri***ellValue()));
} else {//小数
val = Double.parseDouble(new DecimalFormat("0.00").format(cell.getNumeri***ellValue()));
}
}
} 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、对读取数据封装进行测试
package ***.jdh.poi.excel.utils;
import ***.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();
}
}
}
}
五、对数据导出进行封装
1、封装数据导出相关功能
package ***.jdh.poi.excel.utils;
import ***.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指定实体类导出数据或者模板
*/
public class PoiExcelWriteOperation {
/**
* 根据指定实体类,导出excel文档数据
*
* @param outputStream 文件输出流
* @param dataMap 需要导出的数据,key为sheet名称,v为需要导出的数据,若传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(***parator.***paring(el -> el.getAnnotation(PoiExcel.class).cellIndex()))
.collect(Collectors.toList());
fieldList.forEach(f -> f.setA***essible(true));
//若对应的实体类的字段属性未指定PoiExcel注解则不认为导出
if (fieldList.size() == 0) return;
if (dataMap.size() == 0) {
dataMap.put("sheet", null);
}
//所有需要导出excel对应sheet的名称
ArrayList<String> sheetNameList = new ArrayList<>(dataMap.keySet());
//循环当前需要创建的sheet
for (int i = 0; i < dataMap.size(); i++) {
//创建sheet表
XSSFSheet sheet = workbook.createSheet(sheetNameList.get(i));
//需要导出的数据集合
List<T> ts = dataMap.get(sheetNameList.get(i));
//如果对应的数据集合为空,则为模板导出模式
int rowCount = Objects.isNull(ts) ? 0 : ts.size();
Row headRow = sheet.createRow(0);//创建一行数据
//下面创建表头数据
headRow.setHeightInPoints(35);//这个是以像素点为基础
//添加表头数据
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);
}
//循环当前的行(不含表头)
for (int r = 0; r < rowCount; r++) {
Row row = sheet.createRow(r + 1);
T t = ts.get(r);//某一行的数据
//循环列对每一个单元格赋值
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、对导出数据封装进行测试
package ***.jdh.poi.excel.utils;
import ***.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\\";
//这个数据是模拟从数据库查询的数据
public static final List<Admin> data = new ArrayList<>(Arrays.asList(
new Admin(1, "admin001", "一区", 8500.05, new Date(), "qq一区管理员"),
new Admin(2, "admin002", "二区", 8700.05, new Date(), "qq二区管理员"),
new Admin(3, "admin003", "三区", 8800.05, new Date(), "qq三区管理员"),
new Admin(4, "admin004", "四区", 8900.05, new Date(), "qq四区管理员"),
new Admin(5, "admin005", "五区", 9500.05, new Date(), "qq五区管理员"),
new Admin(6, "admin006", "六区", 10000.05, new Date(), "qq六区管理员")
));
@Test
void excelWrite() {
//获取一个文件输出流
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(path + "admin模板数据导出_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地址:
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”的首字母缩写,意为“简洁版的模糊实现”。 一、常用文档类型说明 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 => 错误信息表 三、常用单元格数据类型说明 CellType => 单元格类型 => 对应的数值: CellType._NONE => 未知类型,仅限内部使用 => -1 CellType.NUMERIC => 数值型-整数、小数、日期 => 0 CellType.STRING => 字符串型 => 1 CellType.FORMULA => 公式型 => 2 CellType.BLANK => 空单元格-没值,但是有单元格样式 => 3 CellType.BOOLEAN => 布尔型 => 4 CellType.ERROR => 错误单元格 => 5 Tips: 日期数据对应的单元格类型是CellType.NUMERIC,默认以浮点型数显示,显示为日期格式需要设置单元格样式DataFormat, 字符型单元格内容也可以为富文本RichTextString,可以对文本多部分设置字体Font