导入Maven依赖

<!-- 阿里开源EXCEL -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.7</version>
</dependency>

Excel实体类

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.alibaba.excel.metadata.BaseRowModel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ExcelData extends BaseRowModel implements Serializable
{
@ExcelProperty("姓名")
private String userName;

@ExcelProperty("职务")
private String postName;

@ExcelProperty("籍贯")
private String birthPlace;

@ExcelProperty("出生日期")
@DateTimeFormat("yyyy-MM-dd") // "yyyy/MM/dd" "yyyy年MM月dd日HH时mm分ss秒" 等等格式都可以
private String birth;

@ExcelProperty("工资")
@NumberFormat(".##") //#代表任意数字, 注意这里的日期格式和数字格式都必须是字符串类型的
private String salary;
}
  1. @ExcelProperty(value = String[], index = int):
    设置表头信息
    value: 表名称
    index: 列号

可以根据名字或者Index去匹配。当然也可以不写,默认第一个字段就是index=0,以此类推。千万注意,要么全部不写,要么全部用index,要么全部用名字去匹配。千万别三个混着用,除非你非常了解源代码中三个混着用怎么去排序的。

ExcelIgnore 默认所有字段都会和excel去匹配,加了这个注解会忽略该字段

  1. DateTimeFormat 日期转换,用String去接收excel日期格式的数据会调用这个注解。里面的value参照java.text.SimpleDateFormat

  2. ExcelIgnoreUnannotated 默认不加ExcelProperty 的注解的都会参与读写,加了不会参与

  3. NumberFormat 数字转换,用String去接收excel数字格式的数据会调用这个注解。里面的value参照java.text.DecimalFormat

监听器(每读取一行都会调用invoke方法)

package com.glriverside.qixing.personnel.listener;

// 监听 *** 重点
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.glriverside.qixing.personnel.config.ErrorMsg;
import com.glriverside.qixing.personnel.common.ExcelData;
import com.glriverside.qixing.personnel.model.Education;
import com.glriverside.qixing.personnel.model.Post;
import com.glriverside.qixing.personnel.model.User;
import com.glriverside.qixing.personnel.service.EducationService;
import com.glriverside.qixing.personnel.service.PostService;
import com.glriverside.qixing.personnel.service.UserPostService;
import com.glriverside.qixing.personnel.service.UserService;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.slf4j.Logger;

import java.util.List;

import static java.lang.invoke.MethodHandles.lookup;
import static org.slf4j.LoggerFactory.getLogger;

/**
* 解析监听器,
* 每解析一行会回调invoke()方法。
* 整个excel解析结束会执行doAfterAllAnalysed()方法
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
public class ExcelListener<T> extends AnalysisEventListener<T>
{
private static final Logger LOG = getLogger(lookup().lookupClass());

/**
* 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 5;

private UserService userService;

//存储错误信息的集合
private List<ErrorMsg> errorMsgList;

//构造方法传递spring(目的是传递需要的参数进来)
public ExcelListener(UserService userService, List<ErrorMsg> errorMsgList)
{
this.userService = userService;
this.errorMsgList = errorMsgList;
}
/**
* 自定义存储表格数据
*/
private List<User> userList = new ArrayList<>();

/**
* 自定义存储标题结果
*/
private T titleMap;

@Override
public void invoke(T result, AnalysisContext context)
{
//获取当前行号
Integer rowIndex = context.readRowHolder().getRowIndex();
if (rowIndex == 0)
{
this.titleMap = result;
}
else
{
//数据存储到list,供批量处理,或后续自己业务逻辑处理。
if (result instanceof ExcelData)
{
ExcelData excelData = (ExcelData)result;
User user = new User();

int nameLength = excelData.getUserName().length();
if(excelData.getUserName() != null && nameLength < 5 && nameLength > 1)
user.setName(excelData.getUserName());
else if (excelData.getUserName() == null ){
LOG.warn("数据为空! 第{}行, userName", rowIndex);
errorMsgList.add(new ErrorMsg(rowIndex, "姓名为空"));
}
userService.addUser(user, false);
}
}
LOG.info("解析数据第{}行,数据为:{}", rowIndex, result);

// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (list.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
list.clear();
}
}

/**
* 所有数据解析完成了 都会来调用这个方法
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext)
{
LOG.info("导入Excel完成");
saveData();
}

/**
* 加上存储数据库
*/
private void saveData() {
LOG.info("{}条数据,开始存储数据库!", list.size());
demoDAO.save(list);
LOG.info("存储数据库成功!");
}
}

读取表头数据(需要的话在监听器中重写此方法即可)

/**
* 这里会一行行的返回头
*
* @param headMap
* @param context
*/
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
LOGGER.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
}

Controller层

读取单个Sheet

@RestController
@RequestMapping("/import")
public class ImportController
{
private static final Logger LOG = getLogger(lookup().lookupClass());
@PostMapping("/importExcel")
public void importExcel(@RequestParam("file") MultipartFile file, HttpServletRequest request){
try {
ExcelListener excelListener = new ExcelListener<ExcelData>();

EasyExcel.read(new BufferedInputStream(file.getInputStream()),excelListener).head(ExcelData.class).sheet().doReadSync();
//其他写法
//EasyExcel.read(new BufferedInputStream(file.getInputStream()), ExcelData.class, excelListener).sheet().doReadSync();

} catch (Exception e) {
LOG.error(e.getMessage(),e);
}
}
}

读取多个Sheet

一次性读取

EasyExcel.read(new BufferedInputStream(file.getInputStream()), ExcelData.class, excelListener).doReadAll();

//其他写法
EasyExcel.read(new BufferedInputStream(file.getInputStream()),excelListener).head(ExcelData.class).doReadAll();

分批读取

@RestController
@RequestMapping("/import")
public class ImportController
{
private static final Logger LOG = getLogger(lookup().lookupClass());
@PostMapping("/importExcel")
public void importExcel(@RequestParam("file") MultipartFile file, HttpServletRequest request){
ExcelReader excelReader = null;
try {
excelReader = EasyExcel.read(new BufferedInputStream(file.getInputStream())).build();

ReadSheet readSheet1 =
EasyExcel.readSheet(0).head(ExcelData.class).registerReadListener(new ExcelListener()).build();
//其他写法
ReadSheet readSheet2 =
EasyExcel.readSheet(1).head(ExcelData.class).registerReadListener(new ExcelListener()).build();
// 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能
excelReader.read(readSheet1, readSheet2);
} catch (Exception e) {
LOG.error(e.getMessage(),e);
} finally {
if (excelReader != null) {
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.finish();
}
}
}
}

Lambda表达式重构监听器(增加监听器的可扩展性)