文档章节

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

倚楼听风雨_
 倚楼听风雨_
发布于 2017/05/22 13:34
字数 1433
阅读 6284
收藏 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

© 著作权归作者所有

共有 人打赏支持
倚楼听风雨_

倚楼听风雨_

粉丝 52
博文 10
码字总数 8746
作品 1
长沙
私信 提问
加载中

评论(22)

倚楼听风雨_
倚楼听风雨_

引用来自“Kuangcp”的评论

不开源的么...
不好意思,现在才看到这条评论。代码就在文末的“demo下载”
Kuangcp
Kuangcp
list这么骚,泛型能写俩进去?
Kuangcp
Kuangcp
不开源的么...
倚楼听风雨_
倚楼听风雨_
可以自己扩展下,加个入参,加个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将越来越少,将...

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

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

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

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

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

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

素剑步青尘
06/09
1K
13
office工具包开源了,使用Excel导入导出非常方便

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

雅狼工作室
2017/10/20
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Ubuntu18.04 安装MySQL

1.安装MySQL sudo apt-get install mysql-server 2.配置MySQL sudo mysql_secure_installation 3.设置MySQL非root用户 设置原因:配置过程为系统root权限,在构建MySQL连接时出现错误:ERROR...

AI_SKI
今天
3
0
3.6 rc脚本(start方法) 3.7 rc脚本(stop和status方法) 3.8 rc脚本(以daemon方式启动)

3.6-3.7 rc脚本(start、stop和status方法) #!/usr/bin/env python# -*- coding: utf-8 -*-# [@Version](https://my.oschina.net/u/931210) : python 2.7# [@Time](https://my.oschina.......

隐匿的蚂蚁
今天
3
0
Cnn学习相关博客

CNN卷积神经网络原理讲解+图片识别应用(附源码) 笨方法学习CNN图像识别系列 深度学习图像识别项目(中):Keras和卷积神经网络(CNN) 卷积神经网络模型部署到移动设备 使用CNN神经网络进行...

-九天-
昨天
5
0
flutter 底部输入框 聊天输入框 Flexible

想在页面底部放个输入框,结果键盘一直遮住了,原来是布局问题 Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("评论"), ...

大灰狼wow
昨天
4
0
Kernel I2C子系统

备注:所有图片来源于网络 1,I2C协议: 物理拓扑: I2C总线由两根信号线组成,一条是时钟信号线SCL,一条是数据信号线SDA。一条I2C总线可以接多个设备,每个设备都接入I2C总线的SCL和SDA。I...

yepanl
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部