写在前面
需要说明的是,这篇文章的主旨不是要实现一个完美的Excel导入功能,而是希望做到抛砖引玉以及帮助部分同学展开自我思考的思路。
好了,话不多说,我们直接进入正题。
背景定义
我们在完成功能前一般需要梳理一下需求,由于本篇文章旨在引导,所以需求一般会比较简单。
我们定义如下背景:
某公司由于业务需要,现想要自主研发管理系统,这就需要将公司部分数据从原有系统导出(现有系统已支持),然后导入到新系统中。由于公司体量庞大,业务和数据较多,所以公司领导层希望研发部逐步完成数据转移。研发部的第一个功能就是人员信息数据转移,已知原有人员信息模本中盖内容包括:所在部门,姓名,性别,出生日期,年龄,户籍所在地,居住地,就职时间,婚姻状况,教育程度,联系电话,紧急联系人,紧急联系人电话。
由于研发部主要都是Java开发人员,所以研发部决定先用java开发一版demo,于是采集了部分样本数据:人员信息
由于业务庞大,为分工明确,研发部将数据导入分为多个模块(eg: 文件上传,数据转换,数据填充,相关业务初始化,数据入库)。
分析需求
其他模块暂且不分析,我们简要分析一下上述背景中关于数据转换需求:
- Excel文件上传后,需要读入文件内容,然后逐行转换数据为指定对象。
貌似这个功能很简单,我们就直接开始吧。
创建Demo Project
我们尽量精简一点,我们使用Eclipse(或者idea)创建一个Maven项目
初始化好框架代码
读取Excel文件
需要注意的是,我们虽然是要读取文件,所以第一步就是找到文件,所以我们先创建一个FileUtil类
然后完成第一个方法,读取文件:
package cn.com.pfinfo.excel.util;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
/**
* 文件操作工具类
*
* @author cuitpf
*
*/
public class FileUtil {
/**
* 根据文件路径找到对应的文件,并返回文件对象。
*
* @param fileLocation 文件本地路径
* @return 文件对象
* @throws FileNotFoundException
*/
public static File getFile(String fileLocation) throws FileNotFoundException {
if (fileLocation == null) {
throw new RuntimeException("文件地址不能为空");
}
ClassLoader cl = FileUtil.class.getClassLoader();
URL url = (cl != null ? cl.getResource(fileLocation) : ClassLoader.getSystemResource(fileLocation));
if (url == null) {
throw new FileNotFoundException("本地文件不存在");
}
// Load file by URL
try {
URI uri = new URI(url.toString().replaceAll(" ", "%20"));
return new File(uri.getSchemeSpecificPart());
} catch (URISyntaxException ex) {
// Fallback for URLs that are not valid URIs (should hardly ever happen).
return new File(url.getFile());
}
}
}
加载工作簿
这一步怎么都绕不过去的就是依赖apatch-poi。这里给出maven配置依赖的方式
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>${poi.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${poi.version}</version>
</dependency>
其他直接导入jar包的或者其他构建工具的读者请自行添加依赖。
添加了依赖后,我们就可以开始编码了:
package cn.com.pfinfo.excel.util;
import static cn.com.pfinfo.excel.config.ConstantConfig.XLS;
import static cn.com.pfinfo.excel.config.ConstantConfig.XLSX;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import cn.com.pfinfo.excel.config.ConstantConfig;
import cn.com.pfinfo.excel.exception.ImportExcelBaseException;
/**
* 文件操作工具类
*
* @author cuitpf
*
*/
public class FileUtil {
/**
* 根据文件路径找到对应的文件,并返回文件对象。
*
* @param fileLocation 文件本地路径
* @return 文件对象
* @throws FileNotFoundException
*/
public static File getFile(String fileLocation) throws FileNotFoundException {
if (fileLocation == null) {
throw new RuntimeException("文件地址不能为空");
}
ClassLoader cl = FileUtil.class.getClassLoader();
URL url = (cl != null ? cl.getResource(fileLocation) : ClassLoader.getSystemResource(fileLocation));
if (url == null) {
throw new FileNotFoundException("本地文件不存在");
}
// Load file by URL
try {
URI uri = new URI(url.toString().replaceAll(" ", "%20"));
return new File(uri.getSchemeSpecificPart());
} catch (URISyntaxException ex) {
// Fallback for URLs that are not valid URIs (should hardly ever happen).
return new File(url.getFile());
}
}
/**
* 根据文件路径获取文件中的工作簿,如果文件不是{@link ConstantConfig#XLS}或者{@link ConstantConfig#XLSX}格式的,会返回{@code null}对象
*
* @param localFilePath 文件路径
* @return 如果文件格式是excel格式,返回工作簿对象,否则,返回null对象
* @throws ImportExcelBaseException 文件如果受损导致不能打开,将抛出自定义异常。
*/
public static Workbook getWorkbook(String localFilePath) throws ImportExcelBaseException {
try (FileInputStream fileInputStream = new FileInputStream(getFile(localFilePath))) {
if (localFilePath.endsWith(XLS)) {
return new HSSFWorkbook(fileInputStream);
} else if (localFilePath.endsWith(XLSX)) {
return new XSSFWorkbook(fileInputStream);
}
return null;
} catch (IOException e) {
throw new ImportExcelBaseException(e);
}
}
}
建立映射关系
excel如何转换为java对象呢?我们需要建立一个映射关系,告诉程序,比如:当你发现姓名这一列的时候,它对应的对象属性为name。
这样的映射关系我们传统编码中,是这样写的(代码太长,我就用伪代码表示一下了):
read line
read cell from line
get cell.index of line
if(index = 1)
user.name = cell.value
else if (index = 2)
user.sex = cell.value
...
return user
这种方式当然也能实现功能,但如果某天不需要某个属性了,模版中删除了一列,这里的代码改动可能不会太小。所以上面的代码可能不太优雅,我们需要使用优雅的代码来解决问题。
由于jdk1.5推出了注解这个概念,所以在那之后多数公司在实现excel导入的时候,都采用注解的方式进行协助。我们这里也采用注解协助开发。
在此之前我们需要了解几个概念。java是面向对象的编程语言,万物皆对象。所以我们需要先分析编码在我们可能会接触到的对象概念:工作簿,工作表,行,列,单元格。
- 每一个Excel文件都可以看作是一个工作簿,当打开一个Excel文件时,就等于打开了一个Excel工作簿
- 当打开了Excel工作簿后在窗口底部看到的”Sheet“标签表示的是工作表,有几个标签就表示有几个工作表
- 在每个工作表内,被网格线纵横隔开的就是单元格
- 工作表内从左到右的方向被称为行
- 工作表内从上到下的方向被称为列
- 工作簿并非只单纯的包含工作表,它还可以包含图表和宏表等
我们导入数据的时候,就是把一个个工作表导入成为一个个对应的集合,表中的一行数据对应集合中的一个元素。
反过来,我们一个元素就是描述的某个工作表的某一行数据,元素的属性就是对应的这一行的一个个单元格的数据,具体对应关系就是元素属性和列的对应关系
通常,我们会将第一行标记为表头,就像这样:
所以,属性的数据就是对应的表头所在列的数据。
最后总结一下:
- 对象:描述某个工作表的一行数据
- 对象的属性: 描述某个工作表的一行数据的某一列。
现在可以创建注解了。
首先是类注解:
package cn.com.pfinfo.excel.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 工作表注解,用于描述实体Bean对应的工作表
* @author cuitpanfei
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Sheet {
String value() defult "Sheet1";
}
然后是属性注解:
package cn.com.pfinfo.excel.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 工作表注解,用于描述实体Bean对应的工作表
* @author cuitpanfei
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Cell {
/** 对应的列名称 */
String name() default "";
/** 列序号 */
int index();
/** 字段类型对应的格式 */
String format() default "";
/** 校验是否可以为空 */
boolean isEmpty() default true;
}
读取数据
这一步比较冗长,我们先写一个大致代码框架出来(不必要的代码这里都已经略过了)
public <T> List<T> map(String sheetName, Class<T> clazz){
//获取表头映射关系
//检查获取到的映射关系
//加载工作表
//获取工作表 表头
//检查表头和映射关系是否匹配
//根据映射关系和表头的关系加载表数据
//映射数据为泛型数据集合
//返回映射结果
return null;
}
最后
大体的脉络我们清楚了,后续主要是填充好就好了。
(未完待续)
本文由 cuitpanfei 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为:
2020/01/03 11:23