一、目标
快速导入Excel数据到数据库
二、初始化分析
整体流程
实施人员:数据准备-》数据检查
开发人员:数据导入
实施人员:数据验证
数据初始化的过程主要是,将确定格式的数据导入数据库的过程。因为涉及实施人员,为了兼容性,文件格式采用Excel。
数据导入流程
对于开发而言数据导入分为三个流程:文件导入、文件读取,文件解析、插入数据库。接下来我们分别聊下各个流程。
先说文件导入,文件来源分为几个方向:本地上传,OSS下载。在实现时要支持各种情况的导入。
再说文件读取,文件读取指将Excel的数据读到内存中,数据结构和excel本身保持一致。
再说一下文件解析,主要是将读取的excel数据结构转为数据库的结构对象。回想一下,我们导入的Excel每个列主要有以下几种情况:
- 判重:唯一键列(code)不能重复,包括excel内不能重复,并且和数据库不能重复。
- 判存在:对外键的引用要存在,如果是外链编码有时需要转为外链id。
- 类型转换:枚举的中文要转英文;
启用
中文转为true
;一堆用逗号分隔的类型转为List。
这三种需求基本满足了90%的文件导入需求,所以我们的设计最后要完整覆盖这三种需求,减少新增导入时的代码开发量。
最后我们聊聊将转换好的数据结构对象插入数据库。此时根据唯一键可以实现,只新增或者重复时更新两种方案。关于SQL的编写可以查看我的一篇文章:sql解析
三、技术选型
文件导入
文件导入主要是本地文件上传(直接输入InputStream流)和 由用户上传网络后,程序自动下载。本地上传不用说依赖各自前端系统框架,框架不同实现方法不同。对于网络下载,我们采用阿里云OSS作为网络存储媒介,官方给开发提供了完备的SDK。
文件读取
Java领域解析、生成Excel比较有名的框架有Apache poi、jxl等。但他们都存在一个严重的问题就是非常的耗内存。如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会OOM或者JVM频繁的full gc。
easyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。64M内存1分钟内读取75M(46W行25列)的Excel。让我们彻底拒绝OOM!官方入门手册参考
四、程序设计
整体设计
我们将新增三个接口和一个模板方法类用于Excel导入:
- ExcelData
- ExcelData 对象的列 和Excel 文件 的列一一对应
- ExcelDomain
- Domain是DDD(领域驱动设计)的概念,之所以用个是因为我们系统是DDD架构的。没用DDD的可以对应大家熟悉的DTO。文件解析流程就是将EasyExcel读取的ExcelData转为我们自己的数据结构ExcelDomain。
- ExcelRepository_<T extends ExcelDomain>_
- ExcelRepository 给插入数据库流程提供了将ExcelDomain插入数据库的流程
- ExcelTemplate_<T extends ExcelData>_
- ExcelTemplate 是模板方法,在三个流程中起串联作用,主要提供文件下载,文件读取和通用的文件解析方法。
整体流程: ExcelTemplate从OSS下载或本地导入Excel文件,使用easyExcel读取文件到内存中,将读取好的数据list交给各个具体实现类。各个实现类使用ExcelTemplate按照自有逻辑解析成数据库对象,最后调用ExcelRepository将数据插入数据库。
详细设计
- ExcelData
实现类的ExcelData 需要自己实现接口,用于文件读取流程。一般是根据唯一主键是否存在,当作excel里面数据行是否有效的依据。
public interface ExcelData {
/**
* 检验读取到的Excel行是否有效
* @return 本行数据是否有效
*/
boolean isValid();
}
2. ExcelDomain
这个接口只是为了为了实现文件解析流程将外链编码转为外链Id功能对DTO做的约束。
public interface ExcelDomain {
void setId(Long id);
Long getId();
void setCode(String code);
String getCode();
}
- ExcelRepository
插入数据库流程,主要依赖这个接口实现。
public interface ExcelRepository<T extends ExcelDomain> {
/**
* 通过编码批量查询
*
* @param codeList 编码
* @param tableKey 可空的分库分表键
* @return 符合 的对象列表
*/
List<T> listByCodeList(@NonNull List<String> codeList, @Nullable Long tableKey);
/**
* 批量插入
*
* @param list
*/
void batchSave(List<T> list);
}
- ExcelTemplate
这个类是模板方法类。这里引入了一个自己封装的OssTool,主要负责跟阿里云Oss交互,定义这个类可以屏蔽阿里云自己内部细节,实现面向接口编程。ImportDataDistributedLock 这个类是自己实现的一个分布式锁类,主要用于保证:一个文件只能同时有一个人导入数据。ImportDataDistributedLock 的具体实现可以参考:分布式锁的方法论
- importExcelData
- 主要实现 文件导入
- transferExcelData
- 主要实现 文件读取
- 在文件读取流程中,如何知道自类要转为什么结构的ExcelData,这里我们使用了泛型。但是**运行时如何获得实际类的class传给EasyExcel解析**呢? 这是本文的难点,这里的解决方案和gson 及 fastjson 在处理泛型时用到的原理一致。
- processExcelList
- 主要定义 文件解析 接口
- processAndOutLinkCheck、processCodeCheck、processEnable
- 这三个都是给用户用的辅助文件解析的方法。
@Slf4j(topic = "APPLICATION")
public abstract class ExcelTemplate<T extends ExcelData> {
@Resource
private OssTool ossTool;
@Resource
private ImportDataDistributedLock distributedLock;
/**
* 导入文件
* 导入Excel数据,导入优先级为:
* inputStream > dirAndFile > stepEnum 既 :
* 本地文件流 》 网络下载地址 》 默认下载地址
*
* @param operatorId 操作人
* @param stepEnum InitializeStepEnum
* @param inputStream 文件流
* @param dirAndFile 文件下载路径《dir,file》
* @param updateFlag 是初始化数据还是批量更新数据
* @return 文件导入结果
*/
public Result<Boolean> importExcelData(@NonNull Long operatorId, @NonNull InitializeStepEnum stepEnum,
@Nullable InputStream inputSt