文档章节

基于 POI 封装 ExcelUtil 精简的 Excel 导入导出

yzChen233
 yzChen233
发布于 2017/05/22 13:34
字数 1437
阅读 1.5W
收藏 417

poi

本文是使用 org.apache.poi 进行一次简单的封装,适用于大部分 excel 导入导出功能。过程中可能会用到反射,如若有对于性能有极致强迫症的同学,看看就好。

由于 poi 本身只是针对于 excel 等office软件的一个工具包,在一些常规的 excel 导入导出时,还需要再做一次精简的封装,简化代码耦合。

一、现状

本人经历过几家公司的代码封装,导入导出一般存在下面的情况。

1.1 导入

  1. 传入文件地址,返回 Sheet 对象,在业务代码中进行循环遍历,做相对应的类型转换,业务处理(二零零几年的代码框架)
  2. 传入文件地址,返回 List<String, Object> 的对象,外部直接做强转
  3. 传入文件地址,返回 List<String, String> 的对象,外部将字符串对象转换为对应的类型

总结:如果只有上述的选择,本人是比较倾向于第二种,毕竟对外层是非常友好的

1.2 导出

  1. 直接在逻辑代码中进行遍历封装sheet,传入到生成file的方法中(二零零几年的代码框架)
  2. 先循环遍历 List<Model> 对象,转换为 List<Map<String, String>> 对象,带上 fieldName 传入到封装好excel生成的方法中,内部则使用 map.get() 方法操作
  3. 直接将 List<Model> 对象带上 fieldName 传入到封装好excel生成的方法中,内部将 Model 对象转换为 JSONObject,然后使用 jsonObj.get() 方法操作
  4. 先将 List<Model> 转换为 JSONArray ,带上 fieldName 传入到封装好excel生成的方法中,内部将 Model 对象转换为 JSONObject,然后使用 jsonObj.get() 方法操作。(使用这种做法,据分析应该是为了执行 jsonConfig.registerJsonValueProcessor(Date.class, new JsonDateValueProcessor("yyyy-MM-dd HH:mm:ss")); 这行代码,可能是为了解决日期类型格式问题)

总结:如果只有上述的选择,本人是比较倾向于第三种,第三种只遍历一次,并且外部未做处理。但是按第四种模式来看,那么第三种模式还是会存在日期格式问题,这个我们后续再分析如何处理。

二、导入

2.1 方法定义

/**
* excel导入
* @param keys		字段名称数组,如  ["id", "name", ... ]
* @param filePath	文件物理地址
* @return 
* @author yzChen
* @date 2016年12月18日 下午2:46:51
*/
public static List<Map<String, Object>> imp(String filePath, String[] keys)
    throws Exception {}

2.2 循环处理模块

// 遍历该行所有列
for (short j = 0; j < cols; j++) {
    cell = row.getCell(j);
    if(null == cell) continue;	// 为空时,下一列
    
    // 根据poi返回的类型,做相应的get处理
    if(Cell.CELL_TYPE_STRING == cell.getCellType()) {
        value = cell.getStringCellValue();
    } else if(Cell.CELL_TYPE_NUMERIC == cell.getCellType()) {
        value = cell.getNumericCellValue();
        
        // 由于日期类型格式也被认为是数值型,此处判断是否是日期的格式,若时,则读取为日期类型
        if(cell.getCellStyle().getDataFormat() > 0)  {
            value = cell.getDateCellValue();
        }
    } else if(Cell.CELL_TYPE_BOOLEAN == cell.getCellType()) {
        value = cell.getBooleanCellValue();
    } else if(Cell.CELL_TYPE_BLANK == cell.getCellType()) {
        value = cell.getDateCellValue();
    } else {
        throw new Exception("At row: %s, col: %s, can not discriminate type!");
    }
    
    map.put(keys[j], value);
}

2.3 使用

String filePath = "E:/order.xls";
String[] keys = new String[]{"id","brand"};

List<Map<String, Object>> impList;
try {
    impList = ExcelUtil.imp(filePath, keys);
    
    for (Map<String, Object> map : impList) {
        System.out.println(map.get("brand"));
    }
} catch (Exception e) {
    e.printStackTrace();
}

2.4 分析

  1. 入口只需要传入文件名称,以及外部需要读取的key即可
  2. 内部处理,则针对 数值型、日期类型、字符串 类型已经做了对应处理,外部则直接进行强转对应的类型即可

三、导出

3.1 方法定义

/**
* excel导出
* @param fileNamePath	导出的文件名称
* @param sheetName	导出的sheet名称
* @param list		数据集合
* @param titles		第一行表头
* @param fieldNames	字段名称数组
* @return
* @throws Exception    
* @author yzChen
* @date 2017年5月6日 下午3:53:47
*/
public static <T> File export(String fileNamePath, String sheetName, 
    List<T> list, String[] titles, String[] fieldNames) throws Exception {}

3.2 循环处理模块

// 遍历生成数据行,通过反射获取字段的get方法
for (int i = 0; i < list.size(); i++) {
    t = list.get(i);
    HSSFRow row = sheet.createRow(i+1);
    Class<? extends Object> clazz = t.getClass();
    for(int j = 0; j < fieldNames.length; j++){
        methodName = "get" + capitalize(fieldNames[j]);
        try {
            method = clazz.getDeclaredMethod(methodName);
        } catch (java.lang.NoSuchMethodException e) {	//	不存在该方法,查看父类是否存在。此处只支持一级父类,若想支持更多,建议使用while循环
            if(null != clazz.getSuperclass()) {
                method = clazz.getSuperclass().getDeclaredMethod(methodName);
            }
        }
        if(null == method) {
            throw new Exception(clazz.getName() + " don't have menthod --> " + methodName);
        }
        ret = null == method.invoke(t) ? null : method.invoke(t) + "";
        setCellGBKValue(row.createCell(j), ret + "");
    }
}

3.3 使用

String[] titles = new String[]{"Id", "Brand"};
String[] fieldNames = new String[]{"id", "brand"};
List<Order> expList = new ArrayList<Order>();
Order order = new Order();
order.setId(1L);
order.setBrand("第三方手动阀");
expList.add(order);
order = new Order();
order.setId(2L);
order.setBrand("scsdsad");
expList.add(order);

String fileNamePath = "E:/order.xls";
try {
    ExcelUtil.export(fileNamePath, "订单", expList, titles, fieldNames);
} catch (Exception e) {
    e.printStackTrace();
}

3.4 总结

  1. 入口主要是需要传入 List<Model> 数据集合,以及 fieldNames 字段名称
  2. 内部处理,是直接通过反射获得 get 方法的返回值,进行强转为字符串进行导出
  3. 为了兼容继承父类的一些共有字段的设计,则加上了一层父类的方法读取

四、关于日期类型导出处理

1.1 日期字段导出指定格式内容

  1. 建议在 Model 类中,新增一个扩展字段,并封装一个 get 方法,内容则只是对原字段进行转换,导出时,fieldName 则传递扩展字段即可。如 createTime,示例如下:
private Date createTime;
private String createTimeStr;	// 扩展字段

public Date getCreateTime() {
    return createTime;
}

public void setCreateTime(Date createTime) {
    this.createTime = createTime;
}

public String getCreateTimeStr() {
    createTimeStr = DateUtil.formatDatetime(this.createTime);
    return createTimeStr;
}

五、Demo下载

GJP-Example-ExcelUtil 代码下载

My Blog

blog.guijianpan.com

技术交流

© 著作权归作者所有

yzChen233

yzChen233

粉丝 59
博文 13
码字总数 13943
作品 1
长沙
部门经理
私信 提问
加载中

评论(22)

yzChen233
yzChen233 博主

引用来自“慢慢成长”的评论

百万行(甚至可能更多)excel导入数据库,有没有什么好的方案

引用来自“黑狗”的评论

在之前的基础上,如果有数据库操作,可以多线程写数据,也可以考虑读写分离、批量提交sql、屏蔽事务,等等常用的报表手段
👍
但是加入多线程,会不会把事情复杂化了。。
yzChen233
yzChen233 博主

引用来自“慢慢成长”的评论

百万行(甚至可能更多)excel导入数据库,有没有什么好的方案

引用来自“倚楼听风雨_”的评论

没试过这么大量的。
我个人理解的话,首先,excel肯定是要分 sheet 的,因为目前我看到的 poi 里面返回一个 sheet 最多行数是 int 的最大值。这样的话,也就是每 6万 行数据进行处理,这个就好说了。
如果说 excel 分 sheet 比较麻烦,个人建议还是用 txt、xml、json 更好,行级读取即可。

引用来自“慢慢成长”的评论

额,整型最大值不是 2147483647 么,,,看了下 xlsx的单sheet最大行应该是 1048576,有些特殊情况就是很暴力就是excel格式,还大量数据,
刚才试了一下,xls 格式的,只能存储 65535 行, xlsx 格式可以存储更多,应该是你说的 1048576。
我的示例代码,导出只能导 65535 行记录,估计是 xls 格式的输出原因,导入的话,如果是 xlsx 格式,是可以读取更多的。
像10w多条数据的话,应该也不算多,一定要特殊处理的话,我建议类似做分页一样,传入 currPage、pageSize 进行返回 list 集合。
黑狗
黑狗

引用来自“慢慢成长”的评论

百万行(甚至可能更多)excel导入数据库,有没有什么好的方案
在之前的基础上,如果有数据库操作,可以多线程写数据,也可以考虑读写分离、批量提交sql、屏蔽事务,等等常用的报表手段
黑狗
黑狗

引用来自“慢慢成长”的评论

百万行(甚至可能更多)excel导入数据库,有没有什么好的方案
用不着什么方案 原生的poi api足够了 这么多行你肯定不需要实时,不实时你跑线程执行,异步定时更新处理进度即可。分两个线程,一个线程只管读,读了放buffer,另一个线程从buffer里取,一般读的速度都比你处理的速度快,buffer设置固定的size就是了。api读取数据本来就是一次性只读一部分数据的。
黑狗
黑狗
不用自己造轮子 github一搜一大片
慢慢成长
慢慢成长

引用来自“慢慢成长”的评论

百万行(甚至可能更多)excel导入数据库,有没有什么好的方案

引用来自“倚楼听风雨_”的评论

没试过这么大量的。
我个人理解的话,首先,excel肯定是要分 sheet 的,因为目前我看到的 poi 里面返回一个 sheet 最多行数是 int 的最大值。这样的话,也就是每 6万 行数据进行处理,这个就好说了。
如果说 excel 分 sheet 比较麻烦,个人建议还是用 txt、xml、json 更好,行级读取即可。
额,整型最大值不是 2147483647 么,,,看了下 xlsx的单sheet最大行应该是 1048576,有些特殊情况就是很暴力就是excel格式,还大量数据,
yzChen233
yzChen233 博主

引用来自“慢慢成长”的评论

百万行(甚至可能更多)excel导入数据库,有没有什么好的方案
没试过这么大量的。
我个人理解的话,首先,excel肯定是要分 sheet 的,因为目前我看到的 poi 里面返回一个 sheet 最多行数是 int 的最大值。这样的话,也就是每 6万 行数据进行处理,这个就好说了。
如果说 excel 分 sheet 比较麻烦,个人建议还是用 txt、xml、json 更好,行级读取即可。
yzChen233
yzChen233 博主

引用来自“lx19990999”的评论

跪键盘.com ?
被发现了 :trollface:
yzChen233
yzChen233 博主

引用来自“milin”的评论

为什么不利用注解呢?导入导出注解最简单
个人觉得,没有必要的话,尽量减少在实体类添加注解。假如说,有个实体类,添加 hibernate 字段注解、validate 校验注解、导入导出注解 等等,实体类的代码会很难看。
慢慢成长
慢慢成长
百万行(甚至可能更多)excel导入数据库,有没有什么好的方案
office工具包开源了,使用Excel导入导出非常方便

common.office 详细介绍 java组件,实现Excel,word,pdf等常用office的 #目前只完善Excel的导入导出,方便简洁。具体步骤如下: 1,定义与Excel对应的bean类,加上配置。 2、调用导入导出方...

雅狼工作室
2017/10/20
151
0
崛起于Springboot2.X + 200秒解决文件导入导出(56)

《SpringBoot2.X心法总纲》 使用ExcelUtil快速实现对文件的导入导出系列。 1、pom文件 <dependency> </dependency><dependency> </dependency> 2、实体类 @Data@NoArgsConstructor@AllArgsC......

木九天
2019/11/09
220
4
配置简单功能强大的excel工具类搞定excel导入导出工具类(一)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lk_blog/article/details/8007777 对于J2EE项目导入导出Excel是最普通和实用功能,本工具类使用步骤简单,功能强...

天赋吉运李坤
2012/09/22
0
0
Java +EasyUI+SpringMvc实现Excle导入导出(上)

前言 先介绍下项目的开发环境,采用java开发语言进行开发,前台UI使用的是EasyUI框架,接下来是SpringMvc与Ejb结合来开发,整个项目管理采用的是Maven管理,服务器用的是Jboss6.2.0企业版。 ...

fightingKing
2015/01/26
0
0
SSM框架基于poi实现excel文件的上传以及导入mysql数据库

最近正在完善之前和小伙伴假期做的一个项目的一些小功能,其中一个就是上传excel文件并且将excel文件中的数据导入到指定数据库中。在这其中有遇到一些很小的细节的问题,所以决定将这个实现过...

Carol998
2018/12/29
270
0

没有更多内容

加载失败,请刷新页面

加载更多

速度收藏!看完这份知识图谱,才算搞懂 Flink!

先跟大家分享一个好消息!即日起,Apache Flink 社区微信公众号 Ververica 正式更名为 Flink 中文社区 并由 Apache Flink PMC 成员进行维护,是国内唯一的 Flink 社区官方微信公众号,详细信...

一肥仔
38分钟前
23
0
在C#中使用Global Mutex的良好模式是什么?

Mutex类非常容易被误解,而全局互斥体更是如此。 创建全局互斥锁时,可以使用哪种良好,安全的模式? 一个会起作用的 无论我的机器位于哪个区域 保证正确释放互斥锁 如果没有获取互斥,可以选...

javail
45分钟前
70
0
从开发到生产上线,如何确定集群规划大小?

在 Flink 社区中,最常被问到的问题之一是:在从开发到生产上线的过程中如何确定集群的大小。这个问题的标准答案显然是“视情况而定”,但这并非一个有用的答案。本文概述了一系列的相关问题...

阿里云官方博客
46分钟前
80
0
Linux就该这么学 -- 命令 -- top&uptime&free

top top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用情况,常用于服务端性能分析。 在top命令中按f键后,可进入设置页面,可设置显示或隐藏对应的列,可设置按某...

jionzhao
49分钟前
49
0
Go - atomic包使用及atomic.Value源码分析

1. Go中的原子操作 原子性:一个或多个操作在CPU的执行过程中不被中断的特性,称为原子性。这些操作对外表现成一个不可分割的整体,他们要么都执行,要么都不执行,外界不会看到他们只执行到...

Java天天
52分钟前
54
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部