操作Excel的框架有很多,能实现自己需要的功能才是合适的!
本来以为有那么多框架,会很容易,结果就是赤裸裸的打脸,引入的东西总是多多少少有些问题,找了一圈,发现还是某里比较给力的,那就记录一下
1.引入maven
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.6</version>
</dependency>
2-1.简单读取文件(参数传递路径)
@Override
public String readExcelSave(String filePath) throws Exception {
log.info("===读文件===");
// 有个很重要的点 ExcelDepotExportListener不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
// 写法1:
//String fileName = FileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, DemoData.class, new ExcelDepotExportListener()).sheet().doRead();
// 写法2:项目用参数形式
//fileName = FileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
ExcelReader excelReader = null;
try {
//获取项目中地址
// String fileName = FileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// sheetNo --> 读取哪一个 表单
excelReader = EasyExcel.read(filePath, DemoData.class, new ExcelDepotExportListener()).build();
ReadSheet readSheet = EasyExcel.readSheet(0).build();
excelReader.read(readSheet);
} catch (Exception e) {
log.error(e.getMessage(),e);
}finally {
if (excelReader != null) {
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.finish();
}
}
return "success";
}
2-2.简单读取文件(参数传递文件流)
public String readExcelFile(MultipartFile file) throws Exception {
ExcelReader excelReader = null;
try {
// sheetNo --> 读取哪一个 表单
byte [] byteArr=file.getBytes();
InputStream inputStream = new ByteArrayInputStream(byteArr);
excelReader = EasyExcel.read(inputStream, DemoExcel.class, new ExcelDepotExportListener()).build();
ReadSheet readSheet = EasyExcel.readSheet(0).build();
excelReader.read(readSheet);
} catch (Exception e) {
log.error(e.getMessage(),e);
}finally {
if (excelReader != null) {
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.finish();
}
}
return null;
}
3.监听器
/**
* 模板的读取类
*
*
*/
// 有个很重要的点 ExcelDepotExportListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
public class ExcelDepotExportListener extends AnalysisEventListener<GrainDepotExcel> {
private static final Logger LOGGER = LoggerFactory.getLogger(ExcelDepotExportListener.class);
/**
* 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 5;
List<GrainDepotExcel> list = new ArrayList<GrainDepotExcel>();
/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private DemoService demoService;
public ExcelDepotExportListener() {
// 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
demoService= new demoServiceImpl();
}
/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param demoServiceService
*/
public ExcelDepotExportListener(DemoServiceService demoService) {
this.demoService= demoService;
}
/**
* 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
*
* @param exception
* @param context
* @throws Exception
*/
@Override
public void onException(Exception exception, AnalysisContext context) {
LOGGER.error("解析失败,但是继续解析下一行:{}", exception.getMessage());
// 如果是某一个单元格的转换异常 能获取到具体行号
// 如果要获取头的信息 配合invokeHeadMap使用
if (exception instanceof ExcelDataConvertException) {
ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
LOGGER.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(),
excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());
}
}
/**
* 这里会一行行的返回头
*
* @param headMap
* @param context
*/
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
LOGGER.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
}
/**
* 这个每一条数据解析都会来调用
*
* @param data
* one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(GrainDepotExcel grainDepotExcel, AnalysisContext context) {
LOGGER.info("解析到一条数据:{}", JSON.toJSONString(grainDepotExcel));
//这里可以加逻辑
list.add(saveDepot);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (list.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
list.clear();
}
}
/**
* 所有数据解析完成了 都会来调用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
LOGGER.info("所有数据解析完成!");
}
/**
* 加上存储数据库
*/
private void saveData() {
LOGGER.info("{}条数据,开始存储数据库!", list.size());
demoService.insertBatch(list);
LOGGER.info("存储数据库成功!");
}
}
FileUtil类
public class FileUtil {
public static InputStream getResourcesFileInputStream(String fileName) {
return Thread.currentThread().getContextClassLoader().getResourceAsStream("" + fileName);
}
public static String getPath() {
return FileUtil.class.getResource("/").getPath();
}
public static File createNewFile(String pathName) {
File file = new File(getPath() + pathName);
if (file.exists()) {
file.delete();
} else {
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
}
return file;
}
public static File readFile(String pathName) {
return new File(getPath() + pathName);
}
public static File readUserHomeFile(String pathName) {
return new File(System.getProperty("user.home") + File.separator + pathName);
}
}
完整的栗子可以移步https://www.yuque.com/easyexcel/doc/read,这里只是适合自己项目稍微改了一下逻辑,可以运行正常