文档章节

基于 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 博主

引用来自“Kuangcp”的评论

不开源的么...
不好意思,现在才看到这条评论。代码就在文末的“demo下载”
Kuangcp
Kuangcp
list这么骚,泛型能写俩进去?
Kuangcp
Kuangcp
不开源的么...
yzChen233
yzChen233 博主
可以自己扩展下,加个入参,加个if就行
开源老码农
开源老码农
能不能指定导入某个 sheet?
黑狗
黑狗

引用来自“生吃番茄酱”的评论

引用来自“黑狗”的评论

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

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

引用来自“黑狗”的评论

在之前的基础上,如果有数据库操作,可以多线程写数据,也可以考虑读写分离、批量提交sql、屏蔽事务,等等常用的报表手段

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

👍
但是加入多线程,会不会把事情复杂化了。。
只是一个思路 具体情况还是要具体分析

多线程,数据顺序怎么控制
excel导入没有碰到还有事务顺序的情况。。。有顺序的你放到一个线程里啊
Aschrius
Aschrius

引用来自“黑狗”的评论

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

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

引用来自“黑狗”的评论

在之前的基础上,如果有数据库操作,可以多线程写数据,也可以考虑读写分离、批量提交sql、屏蔽事务,等等常用的报表手段

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

👍
但是加入多线程,会不会把事情复杂化了。。
只是一个思路 具体情况还是要具体分析

多线程,数据顺序怎么控制
_森屿海巷_
_森屿海巷_
试试看
傻傻躺着
傻傻躺着
可以考虑实现根据用户选择的列来动态导出数据,
黑狗
黑狗

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

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

引用来自“黑狗”的评论

在之前的基础上,如果有数据库操作,可以多线程写数据,也可以考虑读写分离、批量提交sql、屏蔽事务,等等常用的报表手段

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

👍
但是加入多线程,会不会把事情复杂化了。。
只是一个思路 具体情况还是要具体分析
ExcelUtil 1.5.5 发布,修复导出属性为空报空指针异常的 bug

ExcelUtil 1.5.5 已发布。 首先本次版本的发布要感谢@superYue ,感谢@superYue在使用工具的时候发现bug.好让我有机会修复。 相信随着大家的参与,ExcelUtil将越来越成熟,bug将越来越少,将...

素剑步青尘
2018/06/26
604
2
ExcelUtil 1.5.2 发布,新增导出功能,优化时间导入 bug

ExcelUtil 借助反射和 POI 对 Excel 读取,省略了以往读取 Excel 的繁琐步骤,调用 ExcelUtil 只需要2步,对,你没有看错,2步足以读取到 Excel 的内容。自动赋值,传入对应Model所在路径,即可...

素剑步青尘
2018/06/07
2.1K
5
ExcelUtil 1.5.3 发布,新增流导出支持直接输出到浏览器中,就是这么勤劳。

ExcelUtil 借助反射和 POI 对 Excel 读取,省略了以往读取 Excel 的繁琐步骤,调用 ExcelUtil 只需要2步,对,你没有看错,2步足以读取到 Excel 的内容。自动赋值,传入对应Model所在路径,即可...

素剑步青尘
2018/06/07
1.3K
5
ExcelUtil 1.5.4 发布,新增参数类,读取指定 Sheet 功能

ExcelUtil 1.5.4 已发布。 ExcelUtil 借助反射和 POI 对 Excel 读取,省略了以往读取 Excel 的繁琐步骤,调用 ExcelUtil 只需要2步,对,你没有看错,2步足以读取到 Excel 的内容.兼容 03/0...

素剑步青尘
2018/06/09
1.9K
15
ExcelUtil —— Excel 便捷读取工具

ExcelUtil 借助反射和 POI 对 Excel 读取,省略了以往读取 Excel 的繁琐步骤,调用 ExcelUtil 只需要2步,对,你没有看错,2步足以读取到 Excel 的内容。...

王练
2017/06/08
132
0

没有更多内容

加载失败,请刷新页面

加载更多

程序员必须掌握的核心算法有哪些?

由于我之前一直强调数据结构以及算法学习的重要性,所以就有一些读者经常问我,数据结构与算法应该要学习到哪个程度呢?,说实话,这个问题我不知道要怎么回答你,主要取决于你想学习到哪些程...

摩尔12
29分钟前
39
0
汇桔网被曝拖欠12月份工资至今,强制买产品,CEO称去年交易额超400亿

三言财经消息,近日有汇桔网员工爆料,汇桔网拖欠12月工资至今,通知延迟到4月才发放。 此外爆料还指出,“各种手法逼迫大家离开,员工要么继续忍受,要么主动离职,要么停薪留职。都不同意只...

小特工作室
30分钟前
34
0
「网易官方」极客战记(codecombat)攻略-地牢-明智的攻击attack-wisely

每个门后都有Ogres--你应该选择哪一个? 由玩家赖曼团创造。 默认代码 # 不要踩在火灾陷阱! hero.moveUp(); hero.moveRight(); hero.moveUp(); # ∆ 移除这行 有些食人魔比别人强大! 只有击...

极客战记
31分钟前
34
0
如何从源代码安装R软件包?

一位朋友给我发送了关于R的网上抓取这个很棒的教程。 我真的很想尝试。 但是,第一步是从源代码安装一个名为RJSONIO的软件包。 我对R非常了解,但是我不知道如何从源代码安装软件包。 我正在...

技术盛宴
41分钟前
44
0
nginx http模块配置合并

在配置nginx.conf文件的时候,我们很容易发现,有部分配置项是既可以配置在http块,也可以配置在server块,还可以配置在location块中。但是并不是所有的配置项都可以在任意位置进行配置的,根...

爱宝贝丶
43分钟前
48
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部