1 PoI中Excel组成
Excel的文件的组织形式,一个Excel文件对应于一个workbook(HSSFWorkbook),一个workbook可以有多个sheet(HSSFSheet)组成,一个sheet是由多个row(HSSFRow)组成,一个row是由多个cell(HSSFCell)组成。
2 业务流程
POI在项目中用于业务数据(比如说用户)的导入导出。分为三个部分:
(1)导出Excel模板,确保用户按照我们给的格式填写,这样方便导入
(2)导入填写好的模板,解析为数据集合
(3)导出数据库中的数据到表格
maven坐标:
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>
3 导出excel模板
效果:
业务代码:创建表格,创建标题、表头
public Workbook createExcelTemplate() {
HSSFWorkbook workbook = new HSSFWorkbook();
//表头(每一列的名字以及填写要求)
List<String> commonHeaders = Arrays.asList(
"所在行政区域|(必填,例如:滨海新区)",
"所在街道办事处|(必填,例如:辖南街道)",
"所在道路及门牌号|(必填)", "小区名称|(必填)",
"楼栋编号|(例如:1)", "单元数量|(例如:2)",
"楼层数量|(例如:25)", "每层每个单元的户数|(例如:2)");
ExcelTool.createSheetWithRichTextHeader(workbook, "template",
ExcelTool.convertToRichText(workbook, commonHeaders));
List<String> variableHeaders = Arrays.asList("小区基本信息|(必填)", "楼栋信息,每栋楼对应一行|(可以不填)");
ExcelTool.createVariableCellSizeHeaderWithRichText(workbook, "template",
ExcelTool.convertToRichText(workbook, variableHeaders), 4);
//表格标题行
ExcelTool.createTitleRow(workbook, "template", "小区信息表", 8);
return workbook;
}
工具类方法:创建富文本表头
/**
* 创建富文本表头
* @param workbook 表格实体
* @param headers 表头,使用"|"作为分隔符,分隔符前面的是黑色字体,分隔符后边将会变成红色字体,例如“属性名称|(属性填写要求)”
* @return 富文本
*/
public static List<RichTextString> convertToRichText(Workbook workbook, List<String> headers){
List<RichTextString> richTextStrings = new ArrayList<>();
for(String head : headers){
String[] headSplit = StringUtils.split(head, "|");
String wholeHead = StringUtils.remove(head, "|");
RichTextString richTextString = new HSSFRichTextString(wholeHead);
richTextString.applyFont(0, headSplit[0].length(), getHeadFont(workbook));
if(headSplit.length > 1){
richTextString.applyFont(headSplit[0].length() , wholeHead.length(), getMouldCellFont(workbook));
}
richTextStrings.add(richTextString);
}
return richTextStrings;
}
4 导入数据
利用反射导入Excel,注意,表格中列的顺序应该和类中字段的顺序一致
/**
* 从表格中解析数据,注意表格中各个属性的顺序要和java类中一致,本工具类从第四行开始读数据,前三行默认为标题行
* @param workbook
* @param clazz
* @return
* @throws IllegalAccessException
* @throws InstantiationException
*/
public List<T> getDataFromExcel(Workbook workbook, Class<T> clazz) throws IllegalAccessException, InstantiationException {
Field[] fields = clazz.getDeclaredFields();
List<T> dataList = new ArrayList<>();
// 解析sheet
for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
Sheet sheet = workbook.getSheetAt(i);
int readRowCount = sheet.getPhysicalNumberOfRows();
// 解析sheet 的行
for (int j = ROW_DATA_INDEX; j < readRowCount; j++) {
Row row = sheet.getRow(j);
if (row == null) {
continue;
}
if (row.getFirstCellNum() < 0) {
continue;
}
// 解析sheet 的列
T t = clazz.newInstance();
for (int k = 0; k < fields.length; k++) {
Cell cell = row.getCell(k);
Field field = fields[k];
field.setAccessible(true);
if(cell == null) {
continue;
}
cell.setCellType(CellType.STRING);
if(field.getType() == String.class){
field.set(t, cell.getStringCellValue());
} else if (field.getType() == Integer.class){
field.set(t, Integer.parseInt(cell.getStringCellValue()));
} else if(field.getType() == Double.class) {
field.set(t, Double.valueOf(cell.getStringCellValue()));
} else if(field.getType() == Float.class) {
field.set(t, Float.valueOf(cell.getStringCellValue()));
}
// todo 添加更多类型
}
dataList.add(t);
}
}
return dataList;
}
5、导出数据
业务代码:查询数据、创建表格、下载文件
//查询数据
List<WorkloadDTO> villageQueryDTOS = reportService.queryWorkload(ip, index, days, sort, direction);
String name = sm.getString("workload.name");
String code = sm.getString("workload.code");
String human = sm.getString("workload.human");
String camera = sm.getString("workload.camera");
//创建表格
Workbook export = ExcelTool.createCommonWorkbookWithCommonHeader(villageQueryDTOS, Arrays.asList(name, code, human, camera), "工作量统计");
//下载
ExcelTool.downloadBrowser(export, response, sm.getString("export_workload.xls", sm.getString("days."+ days)), request);
工具类中方法:通过反射来导出对象中的所有一级属性
/**
* 导出对象中的所有一级属性
* @param workbook
* @param sheet
* @param dataList
* @return
* @throws IllegalAccessException
*/
public static Workbook createCommonWorkbook(Workbook workbook, Sheet sheet, List dataList) throws IllegalAccessException {
CellStyle defaultCellStyle = ExcelTool.getCommonCellStyle(workbook);
// 遍历集合数据,产生数据行
Row row;
int index = ROW_DATA_INDEX;
for (Object data : dataList) {
row = sheet.createRow(index);
row.setHeightInPoints(25);
int cellIndex = 0;
//获取集合元素的类类型,也就是要下载的类的类类型
Class c = data.getClass();
Field[] fields = c.getDeclaredFields();
//遍历属性名集合,获取每一个要导出的属性的名字
for (Field field : fields) {
//设置属性对象可读
field.setAccessible(true);
//获取data中该属性的值
Object insertToCell = field.get(data);
//创建单元格
Cell cell = row.createCell(cellIndex++);
//设置单元格类型为字符串
cell.setCellType(CellType.STRING);
cell.setCellStyle(defaultCellStyle);
//插入数据
if (insertToCell == null) {
cell.setCellValue("");
} else {
cell.setCellValue("" + insertToCell);
}
}
index++;
}
return workbook;
}
工具类中方法:导出对象中的多级属性
/**
* 通用导出方法,可导出对象属性中的属性,层级数量不限
* @param workbook
* @param sheet
* @param dataList 要导出的数据
* @param fieldNames 属性,如果是属性套属性,则多级属性之间用点号分隔 如“attribute1.attribute2”;
* @return
* @throws NoSuchFieldException
* @throws IllegalAccessException
*/
public static Workbook createCommonWorkbook(Workbook workbook, Sheet sheet, List dataList, List<String> fieldNames) throws NoSuchFieldException, IllegalAccessException {
CellStyle defaultCellStyle = ExcelTool.getCommonCellStyle(workbook);
// 遍历集合数据,产生数据行
Row row;
int index = ROW_DATA_INDEX;
for (Object data : dataList) {
index++;
row = sheet.createRow(index);
row.setHeightInPoints(25);
int cellIndex = 0;
//获取集合元素的类类型,也就是要下载的类的类类型
Class c = data.getClass();
//遍历属性名集合,获取每一个要导出的属性的名字
for (String fieldName : fieldNames) {
//如果是属性中有好几级属性(该字段是引用类型),获取每一级属性的名字
String[] fields = fieldName.split("\\.");
//定义要插入到数据库中的数据对象
Object insertToCell = null;
for (int i = 0; i < fields.length; i++) {
//i== 0说明是第一级属性
if (i == 0) {
//从data中根据属性名获取属性对象
Field nameField = c.getDeclaredField(fields[i]);
//设置属性对象可读
nameField.setAccessible(true);
//获取data中该属性的值
insertToCell = nameField.get(data);
//如果第一级属性为null,则不解析下一级属性
if (insertToCell == null) {
break;
}
} else {
//从上一级属性值中获取属性对象
Field nameField = insertToCell.getClass().getDeclaredField(fields[i]);
//设置可读
nameField.setAccessible(true);
//获取上一级属性对象中该属性的值
insertToCell = nameField.get(insertToCell);
}
}
//创建单元格
Cell cell = row.createCell(cellIndex++);
//设置单元格类型为字符串
cell.setCellType(CellType.STRING);
cell.setCellStyle(defaultCellStyle);
//插入数据
if (insertToCell == null) {
cell.setCellValue("");
} else {
cell.setCellValue("" + insertToCell);
}
}
}
return workbook;
}
工具类方法:下载excel文件
/**
* 下载到客户端浏览器
*
* @param workbook 要下载的Excel表格文件
* @param response 响应
* @param fileName 文件名称
* @throws IOException io流异常
*/
public static void downloadBrowser(Workbook workbook, HttpServletResponse response, String fileName, HttpServletRequest request) throws IOException {
request.setCharacterEncoding("UTF-8");
// 清空response
response.reset();
// 设置response的响应头Header,控制浏览器以下载的形式打开文件
response.setHeader("Content-disposition",
"attachment;" + encodeFileName(request, fileName));
//获得输出流,包装成缓冲流可以提高输入、输出效率。但需要写flush方法才能清空缓冲区
OutputStream toClient = new BufferedOutputStream(
response.getOutputStream());
//设置文件类型和编码格式
response.setContentType("application/vnd.ms-excel;charset=utf-8");
//将表格数据直接写入到输出流
workbook.write(toClient);
//flush方法迫使缓冲的输出数据被写出到底层输出流中,其实其内部也是调用write方法
toClient.flush();
//关闭流
toClient.close();
}
注释在代码中比较完善了,就不一一赘述了。代码请见:
https://github.com/mambo-wang/tools/blob/master/ExcelTool.java
总结:
最近一年做的项目中多次用到了表格导入导出功能,因此总结出这样一个工具类,主要用到的poi功能有:
(1)创建表格、sheet、row、cell、富文本
(2)调整文字颜色、粗细、字体、字号
(3)合并单元格、调整单元格外框粗细
希望需要的人可以用得上~