easyExcel通用导入工具类
需求前景
近期在做几个关于excel
文件上传导入数据库的操作,于是便在Easyexcel
的基础上造了个轮子。
主要涉及的技术有
- 反射
@FunctionalInterface
函数式接口
轮子开始
函数式接口:ThrowingConsumer
import java.util.function.Consumer;
/**
* @version 1.0.0
* @className: ThrowingConsumer
* @description: 因为自带的Consumer不会抛出异常,这里我们重写了一个可以抛出异常的Consumer
* @author: LiJunYi
* @create: 2022/9/9 13:58
*/
@FunctionalInterface
public interface ThrowingConsumer<T> extends Consumer<T>
{
/**
* 重写accept方法,捕获并抛出自定义业务异常
*
* @param t t
*/
@Override
default void accept(T t) {
try {
acceptBase(t);
} catch (Exception ex) {
throw new RRException(ex.getMessage());
}
}
/**
* 对给定参数执行消费操作
*
* @param t t
*/
void acceptBase(T t);
}
EasyExcelImportUtil
/**
* @version 1.0.0
* @className: EasyExcelImportUtil
* @description: easyExcel通用导入解析
* @author: LiJunYi
* @create: 2022/9/9 13:55
*/
public class EasyExcelImportUtil<T>
{
/**
* 使用自定义监听器
*
* @param listener 自定义监听器
* @param fileStream 文件流
* @param elementType 元素类型
*/
public static <T> void importFile(InputStream fileStream, Class<T> elementType, ReadListener<T> listener) {
EasyExcel.read(fileStream, elementType, listener).sheet().doRead();
}
/**
* 通用导入excel文件方法
*
* @param fileStream 导入的文件流
* @param elementType 接收excel每行数据的实体类型
* @param serviceAction 数据解析完成后调用的业务逻辑方法
* @param <T> 实体类型
*/
public static <T> void importFile(InputStream fileStream, Class<?> elementType, ThrowingConsumer<List<T>> serviceAction) {
// 获取excel通用监听器
EasyExcelImportCommonListener<T> commonListener = new EasyExcelImportCommonListener<>(serviceAction);
// 读取excel文件并导入
EasyExcel.read(fileStream,elementType, commonListener).sheet().doRead();
}
/**
* 通用导入excel文件方法
*
* @param fileStream 导入的文件流
* @param elementType 接收excel每行数据的实体类型
* @param serviceAction 数据解析完成后调用的业务逻辑方法
* @param parameter 其他参数
* @param field 字段名称
*/
public static <T> void importFile(InputStream fileStream, Class<?> elementType, ThrowingConsumer<List<T>> serviceAction, Map<String, ?> parameter, String field) {
// 获取excel通用监听器
EasyExcelImportCommonListener<T> commonListener = new EasyExcelImportCommonListener<>(serviceAction,parameter,field);
// 读取excel文件并导入
EasyExcel.read(fileStream,elementType, commonListener).sheet().doRead();
}
}
EasyExcelImportCommonListener
/**
* @version 1.0.0
* @className: EasyExcelImportCommonListener
* @description: EasyExcel通用导入时监听处理器
* @author: LiJunYi
* @create: 2022/9/9 13:56
*/
@Slf4j
public class EasyExcelImportCommonListener<T> implements ReadListener<T>
{
/**
* 每隔 200 条存储数据库,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 200;
/**
* 转换后插入数据表的实体
*/
private List<T> persistentDataList = Lists.newArrayList();
/**
* 具体业务逻辑接口方法
*/
private final ThrowingConsumer<List<T>> persistentActionMethod;
/**
* 需要设置对应字段时的参数及数值缓存
*/
private Map<String, ?> parameter = null;
/**
* 字段名,同时也是 parameter 的 key
*/
private String field = null;
/**
* 构造函数注入:不包括其他参数
*
* @param persistentActionMethod 具体业务逻辑接口方法
*/
public EasyExcelImportCommonListener(ThrowingConsumer<List<T>> persistentActionMethod) {
this.persistentActionMethod = persistentActionMethod;
}
/**
* 构造函数(包含其他参数)
*
* @param persistentActionMethod 持续操作方法
* @param parameter 参数
* @param field 需要通过反射设置值的字段名
*/
public EasyExcelImportCommonListener(ThrowingConsumer<List<T>> persistentActionMethod, Map<String, ?> parameter, String field) {
this.persistentActionMethod = persistentActionMethod;
this.parameter = parameter;
this.field = field;
}
/**
* 在转换异常 获取其他异常情况下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
*
* @param exception 异常
* @param context 上下文
* @throws Exception 异常
*/
@Override
public void onException(Exception exception, AnalysisContext context) throws Exception {
if (exception instanceof ExcelDataConvertException) {
ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
log.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(),
excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData().getStringValue());
}
}
/**
* 返回每个sheet页的表头
*
* @param headMap 头Map集合
* @param context 上下文
*/
@Override
public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
Map<Integer, String> headMapping = ConverterUtils.convertToStringMap(headMap, context);
if (CollUtil.isEmpty(headMapping))
{
// 抛出自定义业务异常转全局处理
throw new RRException("表头不允许为空!");
}
}
/**
* 每一条数据解析都会来调用
*
* @param t t
* @param analysisContext 分析上下文
*/
@Override
public void invoke(T t, AnalysisContext analysisContext)
{
if (ObjectUtil.isNotNull(field))
{
try {
// 获取指定的字段
Field relationIdField = t.getClass().getDeclaredField(field);
relationIdField.setAccessible(Boolean.TRUE);
// 通过参数获取需要设置的值
Object v = parameter.get(field);
relationIdField.set(t, v);
}catch (Exception e)
{
log.error("excel解析时,通过反射设置对应字段值异常,原因:{}", e.getMessage());
}
}
// 校验导入字段:校验字段同样是通过反射进行实现的
// 可以参考博客中《利用反射比较并记录对象修改的值》这篇文章
// ExcelImportValid.validRequireField(t);
// 存入集合缓存
persistentDataList.add(t);
// 当数据达到最大插入数量后则进行入库操作,防止大数量情况下OOM
if (persistentDataList.size() >= BATCH_COUNT) {
// 进行业务数据插入
this.insertDataToDb(persistentDataList);
// 清空集合
persistentDataList.clear();
}
}
/**
* 所有数据解析完后,回调
*
* @param analysisContext 分析上下文
*/
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
// 在此处调用入库操作,防止有剩余数据未入库
if (CollUtil.isNotEmpty(persistentDataList))
{
insertDataToDb(persistentDataList);
}
}
/**
* 插入数据到数据库
*
* @param data 数据
*/
private void insertDataToDb(List<T> data) {
// 对数据分组,批量插入
List<List<T>> dataList = ListUtil.split(data, BATCH_COUNT);
// 调用业务方法进行后续操作
dataList.forEach(persistentActionMethod);
}
}