首先引入poi依赖
<poi-version>4.0.1</poi-version>
<!--POI-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>${poi-version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${poi-version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>${poi-version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-excelant</artifactId>
<version>${poi-version}</version>
</dependency>
下面是我自己poi封装的结构

先说说普通导入吧:
这是转换单元格字符类型
/**
* @program: ihrm_parent
* @description 转换单元格字符类型
* @author: 影耀子(YingYew)
* @create: 2022-04-10 14:38
**/
public class DataTypeHandle {
public static Object getCellValue(Cell cell) {
//1.获取到单元格的属性类型
CellType cellType = cell.getCellType();
//2.根据单元格数据类型获取数据
Object value = null;
switch (cellType) {
case STRING:
value = cell.getStringCellValue();
break;
case BOOLEAN:
value = cell.getBooleanCellValue();
break;
case NUMERIC:
if(DateUtil.isCellDateFormatted(cell)) {
//日期格式
value = cell.getDateCellValue();
}else{
//数字
value = cell.getNumericCellValue();
}
break;
case FORMULA: //公式
value = cell.getCellFormula();
break;
default:
break;
}
return value;
}
}
基本excel导入的封装
/**
* @program: ihrm_parent 基本excel导入,非百万
* @description 为了做到统⼀的excel解析处理,进⾏通⽤的解析器封装,,放了满⾜⼀边解析数据⼀边存库的需要,通过
* 接⼝实现
* ExcelHandler
* Consumer
* 回调操作,保证解析到⼀定数据量后就可以进⾏后续操作。
* @author: 影耀子(YingYew)
* @create: 2022-06-04 14:34
**/
public class ExcelHandler<T> {
/**
* 批量处理梳理
*/
public Integer batchNum;
/**
* 开始解析的行号,第一行时,为0
*/
public Integer startRow;
/**
* 封装的entity对象
*/
public Class<?> entity;
/**
* 临时解析对象的构造器
*/
public Constructor<?> constructor;
/**
* 传入解析数据的service对象
*/
public Consumer<List<T>> uploadService;
/**
* 接收解析对象值
*/
public List<T> list = new ArrayList<>();
public ExcelHandler(Consumer<List<T>> uploadService, Class<?> entity,Integer batchNum, Integer startRow) {
this.batchNum = batchNum == null?1000:batchNum;
this.startRow = startRow;
this.entity = entity;
this.uploadService = uploadService;
}
/**
* @param inputStream 数据流
* @throws Exception
*/
public void handleData(InputStream inputStream) throws Exception {
// 创建构造器
constructor = entity.getDeclaredConstructor(List.class);
// 创建表格对象
XSSFWorkbook workbook = new XSSFWorkbook(inputStream);
// 有多少个sheet
int sheets = workbook.getNumberOfSheets();
for (int i = 0; i < sheets; i++) {
Sheet sheet = workbook.getSheetAt(i);
// 获取多少行
int rows = sheet.getPhysicalNumberOfRows();
// 第0为表头,第一行开始解析数据
for (int j = 0; j < rows; j++){
parseCellData(sheet,j);
}
}
// 所有数据解析完成后
saveData(list);
}
/**
* 回调保存数据
* @param list
*/
private void saveData(List<T> list) {
if (list.size() > 0) {
uploadService.accept(list);
list.clear();
}
}
/**
* 获取表格数据
* @param sheet
* @param currentRow
*/
private void parseCellData(Sheet sheet, int currentRow) throws Exception {
// 获取第行号
Row row = sheet.getRow(currentRow);
// 解析正文数据
List<Object> values = new ArrayList<>();
for (int i = 0; i < row.getLastCellNum(); i++) {
Cell cell = row.getCell(i);
Object value = DataTypeHandle.getCellValue(cell);
values.add(value);
}
endRow(values,currentRow);
}
/**
* 一行解析完成后
* @param values
* @param currentRow
*/
private void endRow(List<Object> values, int currentRow) throws Exception {
if (currentRow < startRow){
System.out.println("解析头部数据:"+values);
}else {
System.out.println("解析表格数据:"+values);
T data = (T) constructor.newInstance(values);
list.add(data);
}
if (list.size() >= batchNum){
// 回调接口,处理数据
saveData(list);
}
}
}
这是实体类的转换模板,基本字段都比较全面
/**
* @program: ihrm_parent
* @description 实体类转换模板,若用的是百万数据导入的话,全部都是字符串,没有进行类型转换
* @author: 影耀子(YingYew)
* @create: 2022-06-04 15:30
**/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EntityMB {
private Integer currentRow;
private String name;
private Integer agx;
private String gender;
private String address;
private Double height;
private Date date;
private LocalDateTime date1;
// 进⾏解析后的类型转换
public EntityMB(List<Object> values){
this.name = values.get(0)==null?"": values.get(0).toString();
this.agx = values.get(1)==null?0:new Double(values.get(1).toString()).intValue();
this.gender = values.get(2)==null?"": values.get(2).toString();
this.address = values.get(3)==null?"": values.get(3).toString();
this.date = (Date) (values.get(4));
this.date1 = ((Date) (values.get(4))).toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
}
// 百万数据解析后全部都是字符串,需要在这里手动转换
/*public EntityMB(List<String> values){
// 百万数据解析后第一个数据是当前行号
this.currentRow = values.get(0)==null?0:new Double(values.get(0)).intValue();
this.name = values.get(1)==null?"": values.get(1);
this.agx = values.get(2)==null?0:new Double(values.get(2)).intValue();
this.gender = values.get(3)==null?"": values.get(3);
this.address = values.get(4)==null?"": values.get(4);
this.date = DateUtil.parse(values.get(5));
this.date1 = Convert.toLocalDateTime(values.get(5));
}*/
}
使用封装的代码
/**
* 导入用户
* @return
*/
@PostMapping("import")
public R importUser1(@RequestParam(name = "file") MultipartFile file) throws Exception{
// 使用全局导入
Consumer<List<EntityMB>> consumer = this::globalImport;
new ExcelHandler<>(consumer,EntityMB.class,null, 1 )
.handleData(file.getInputStream());
return R.FAIL();
}
private void globalImport(List<EntityMB> entityMBS) {
System.out.println("导入的数据为"+entityMBS);
}
接下来就是百万数据导入代码
/**
* @program: ihrm_parent
* @description 百万数据导出
* @author: 影耀子(YingYew)
* @create: 2022-06-04 16:52
**/
public class XssfSheetHandler {
/**
*
* @param inputStream 数据流
* @param consumer 自定义回调
* @param entityClass 解析数据实体
* @param batchNum 批处理数量
* @param startRow excel解析正文行号
* @throws Exception
*/
public static void handlerData(InputStream inputStream, Consumer consumer,Class<?> entityClass,
Integer batchNum,Integer startRow) throws Exception {
//1.根据excel报表获取OPCPackage
OPCPackage opcPackage = OPCPackage.open(inputStream);
//2.创建XSSFReader
XSSFReader reader = new XSSFReader(opcPackage);
//3.获取SharedStringTable对象
SharedStringsTable table = reader.getSharedStringsTable();
//4.获取styleTable对象
StylesTable stylesTable = reader.getStylesTable();
//5.创建Sax的xmlReader对象
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
//6.注册事件处理器
XSSFSheetXMLHandler xmlHandler = new XSSFSheetXMLHandler(stylesTable,table
,new SheetHandler(batchNum,startRow,entityClass,consumer),false);
xmlReader.setContentHandler(xmlHandler);
//7.逐行读取
XSSFReader.SheetIterator sheetIterator = (XSSFReader.SheetIterator) reader.getSheetsData();
while (sheetIterator.hasNext()) {
InputStream stream = sheetIterator.next(); //每一个sheet的流数据
InputSource is = new InputSource(stream);
xmlReader.parse(is);
}
}
}
然后就是自定义事件处理器
/**
* 自定义的事件处理器
* 处理每一行数据读取
* 实现接口
* @author 影耀子
*/
public class SheetHandler<T> implements XSSFSheetXMLHandler.SheetContentsHandler {
/**
* 批量处理梳理
*/
public Integer batchNum;
/**
* 开始解析的行号,第一行时,为0
*/
public Integer startRow;
/**
* 封装的实体类
*/
public Class<?> entity;
/**
* 当前解析的行号
*/
private Integer currentRow;
/**
* 临时解析对象的构造器
*/
public Constructor<?> constructor;
/**
* 传入解析数据的service对象
*/
public Consumer<List<T>> uploadService;
/**
* 接收解析对象值
*/
public List<T> list = new ArrayList<>();
/**
* 解析头部数据
*/
public List<String> headers = new ArrayList<>();
/**
* 解析单元格
*/
public List<String> valuesList = new ArrayList<>();
/**
* 解析的行号(第几个单元格),默认0为第一列
*/
public Integer cellNum = 0;
/**
* 存储列名与列名下标映射
*/
public Map<Integer, String> map = new HashMap<>();
public SheetHandler(Integer batchNum, Integer startRow, Class<?> entity, Consumer<List<T>> uploadService) throws Exception {
this.constructor = entity.getDeclaredConstructor(List.class);
this.batchNum = batchNum == null?1000:batchNum;
this.startRow = startRow;
this.entity = entity;
this.uploadService = uploadService;
}
/**
* 当开始解析某一行的时候触发
* i:行索引
*/
@Override
public void startRow(int i) {
//实例化对象
currentRow = i;
if (currentRow < startRow){
// 表头数据不添加当前行
return;
}
// 给此行数据记录当前行
valuesList.add(String.valueOf(currentRow + 1));
}
/**
* 当结束解析某一行的时候触发
* i:行索引
*/
@Override
public void endRow(int i) {
try {
cellNum = 0;
if (headers.size() > 0){
System.out.println("解析的头部数据"+headers);
}
if (valuesList.size() > 0){
// 兼容excel表格每行的最后列为null的错误
int valueListSize = map.size();
// 之所以用<=,是因为前面有记录它的当前行
while (valuesList.size() <= valueListSize){
valuesList.add(null);
}
System.out.println("解析的表格数据"+valuesList);
T data = (T) constructor.newInstance(valuesList);
list.add(data);
}
if (list.size() >=batchNum){
// 回调接口,处理数据
saveData(list);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
headers.clear();
valuesList.clear();
}
}
/**
* 对行中的每一个表格进行处理
* cellReference: 单元格名称
* value:数据
* xssfComment:批注
*/
@Override
public void cell(String cellReference, String value, XSSFComment xssfComment) {
try {
// 列名
String cellName = cellReference.replace(String.valueOf(currentRow+1), "");
if (currentRow < startRow){
map.put(cellNum,cellName);
// 加载表头数据
headers.add(value);
}else {
while (!cellName.equals(map.get(cellNum))){
valuesList.add(null);
cellNum++;
}
valuesList.add(value);
}
cellNum++;
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 解析完sheet,多个sheet回调多次
*/
@Override
public void endSheet() {
// 解析完成
saveData(list);
}
/**
* 回调保存数据
* @param list
*/
private void saveData(List<T> list) {
if (list.size() > 0) {
uploadService.accept(list);
list.clear();
}
}
}
调用百万数据导入的代码
/**
* 导入用户
* @return
*/
@PostMapping("import")
public R importUser1(@RequestParam(name = "file") MultipartFile file) throws Exception{
// 百万数据全局导入test
Consumer<List<EntityMB>> consumer = this::globalImport;
XssfSheetHandler.handlerData(file.getInputStream(),consumer,EntityMB.class,null,1);
return R.FAIL();
}
private void globalImport(List<EntityMB> entityMBS) {
System.out.println("导入的数据为"+entityMBS);
}
接下来就是poi的导出封装,借用的hutool工具类
引入依赖<hutool-all>5.8.3</hutool-all>
<!-- hutool工具类,几乎包含所以通用方法 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool-all}</version>
</dependency>
public class Excel {
/**
* 导出excel
* @param data 数据
* @param response 返回信息
* @param returnName 文件名
*/
public void export(List<?> data, HttpServletResponse response, String returnName) {
BigExcelWriter writer= ExcelUtil.getBigWriter();
writer.write(data,true);
ByteArrayOutputStream os = new ByteArrayOutputStream();
writer.flush(os);
writer.close();
new DownloadUtils().download(os,response,returnName);
}
}
/**
* @author 影耀子YingYew
* @Description: 文件下载工具类
* @Package: com.lookvin.common.util.download
* @ClassName: DownloadUtils
* @date 2022/7/27 9:46
*/
public class DownloadUtils {
/**
* 下载文件工具类
* @param byteArrayOutputStream
* @param response
* @param returnName
* @throws IOException
*/
public void download(ByteArrayOutputStream byteArrayOutputStream, HttpServletResponse response, String returnName) {
try {
response.setContentType("application/octet-stream");
returnName = response.encodeURL(new String(returnName.getBytes(),"iso8859-1")); //保存的文件名,必须和页面编码一致,否则乱码
response.addHeader("content-disposition","attachment;filename=" + returnName);
response.setContentLength(byteArrayOutputStream.size());
ServletOutputStream outputstream = response.getOutputStream();
byteArrayOutputStream.writeTo(outputstream); //写到输出流
outputstream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}