1、POI概述
在企業級應用開發中,Excel報表是一種最常見的報表需求。Excel報表開發一般分爲兩種形式:
- 基於Excel報表批量上傳數據
- 通過Java代碼生成Excel報表
Java中常見的用來操作Excel的方式一般有2種:JXL和POI
- JXL只能對Excel進行操作,屬於比較老的框架,它只支持到Excel 95-2000的版本。現在已經停止更新和維護。
- POI是Apache的項目,可對微軟的Word、Excel、PPT進行操作:包括office2003和2007,Excl2003和2007。POI現在一直有更新。
Apache POI是Apache軟件基金會的開源項目,由Java編寫的免費開源的跨平臺的 Java API,Apache POI提供API給Java語言操作Microsoft Office的功能。使用場景:
- 數據報表生成
- 數據備份
- 數據批量上傳
2、準備工作
2.1、創建項目導入依賴
<dependencies>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.0.1</version>
</dependency>
</dependencies>
2.2、POI常用API介紹
2.3、自定義工具類
自定義生成Excel報表文件有很多不盡如意的地方,特別是針對複雜報表頭,單元格樣式,字體等操作。手寫這些代碼不僅費時費力,有時候效果還不太理想。怎麼樣才能更方便的對報表樣式,報表頭進行處理呢?答:使用已經準備好的Excel模板,只需要關注模板中的數據即可。
2.3.1、準備模板文件,比如:
2.3.2、自定義註解
// 用來修飾實體類的字段:指定屬性用來封裝excel表的哪一列數據
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelAttribute {
/** 對應的列名稱 */
String name() default "";
/** 列序號 */
int sort();
/** 字段類型對應的格式 */
String format() default "";
}
2.3.3、創建實體類
// 用來封裝數據庫的每條記錄:對應excel的一條記錄
public class Employee {
// 編號
@ExcelAttribute(sort = 0)
private String id;
// 姓名
@ExcelAttribute(sort = 1)
private String username;
// 手機
@ExcelAttribute(sort = 2)
private String mobile;
// 最高學歷
@ExcelAttribute(sort = 3)
private String edu;
// 籍貫
@ExcelAttribute(sort = 4)
private String province;
// 生日
@ExcelAttribute(sort = 5)
private String birthday;
// 入職時間
@ExcelAttribute(sort = 6)
private Date joinDate;
// 省略setters & getters
}
2.3.4、導出工具類
// 基本模板的Excel導出工具類
public class ExcelExportUtil<T> {
// 寫入數據的起始行
private int rowIndex;
// 需要提取樣式所在的行號
private int styleIndex;
// 對象的字節碼
private Class clazz;
// 對象中的所有屬性
private Field fields[];
// 構造方法
public ExcelExportUtil(Class clazz,int rowIndex,int styleIndex) {
this.clazz = clazz;
this.rowIndex = rowIndex;
this.styleIndex = styleIndex;
fields = clazz.getDeclaredFields();
}
/**
* 基於註解導出
* response:響應對象,用於往瀏覽器導出數據(文件下載)
* is:模板文件對應的輸入流
* objs:要導出的數據,即從數據庫中查詢的對象數據集合
* fileName:導出的文件名(文件下載名)
*/
public void export(HttpServletResponse response,InputStream is, List<T> objs,String fileName) throws Exception {
// 根據模板創建工作簿
XSSFWorkbook workbook = new XSSFWorkbook(is);
// 獲得工作表
Sheet sheet = workbook.getSheetAt(0);
// 獲取公共樣式
CellStyle[] styles = getTemplateStyles(sheet.getRow(styleIndex));
AtomicInteger datasAi = new AtomicInteger(rowIndex);
// 遍歷集合
for (T t : objs) {
// 創建每一行對象
Row row = sheet.createRow(datasAi.getAndIncrement());
for(int i=0;i <styles.length; i++) {
// 創建一個單元格對象(每一列)
Cell cell = row.createCell(i);
// 設置列的樣式
cell.setCellStyle(styles[i]);
// 遍歷成員變量對象
for (Field field : fields) {
// 判斷成員變量上是否使用了ExcelAttribute註解
if(field.isAnnotationPresent(ExcelAttribute.class)){
// 暴露反射
field.setAccessible(true);
// 獲得註解對象
ExcelAttribute ea = field.getAnnotation(ExcelAttribute.class);
if(i == ea.sort()) {
// 設置單元格內容
cell.setCellValue(field.get(t).toString());
}
}
}
}
}
// 將excel文件導出到瀏覽器
fileName = URLEncoder.encode(fileName, "UTF-8");
response.setContentType("application/octet-stream");
response.setHeader("content-disposition", "attachment;filename=" + new String(fileName.getBytes("ISO8859-1")));
response.setHeader("filename", fileName);
workbook.write(response.getOutputStream());
}
/**
* 獲得指定行的單元格樣式
*/
public CellStyle[] getTemplateStyles(Row row) {
CellStyle [] styles = new CellStyle[row.getLastCellNum()];
for(int i=0;i<row.getLastCellNum();i++) {
styles[i] = row.getCell(i).getCellStyle();
}
return styles;
}
// 省略 setters & getters ..................
}
2.3.5、導入工具類
// 基於註解實現Excel報表數據導入
public class ExcelImportUtil<T> {
// 對象的字節碼
private Class clazz;
// 對象的所有屬性
private Field fields[];
public ExcelImportUtil(Class clazz) {
this.clazz = clazz;
fields = clazz.getDeclaredFields();
}
/**
* 基於註解讀取excel文檔數據
* is:上傳文件的字節輸入流
* rowIndex:讀取數據的起始行
* cellIndex:讀取數據的起始單元格索引
*/
public List<T> readExcel(InputStream is, int rowIndex,int cellIndex) {
// 創建集合:用來每一行記錄(對象)
List<T> list = new ArrayList<T>();
// 實體對象
T entity = null;
try {
// 根據字節輸入流創建工作簿
XSSFWorkbook workbook = new XSSFWorkbook(is);
//獲得工作表
Sheet sheet = workbook.getSheetAt(0);
// 獲得總行數
int rowLength = sheet.getLastRowNum();
// 遍歷每行數據
for (int rowNum = rowIndex; rowNum <= sheet.getLastRowNum(); rowNum++) {
// 獲得每一行的行對象
Row row = sheet.getRow(rowNum);
// 通過反射創建實體對象:一行對應一個實體對象
entity = (T) clazz.newInstance();
// 遍歷每一列
for (int j = cellIndex; j < row.getLastCellNum(); j++) {
// 獲得單元格對象
Cell cell = row.getCell(j);
// 遍歷成員變量對象
for (Field field : fields) {
// 判斷成員變量上是否使用了註解:ExcelAttribute
if(field.isAnnotationPresent(ExcelAttribute.class)){
// 暴露反射
field.setAccessible(true);
// 獲得ExcelAttribute註解對象
ExcelAttribute ea = field.getAnnotation(ExcelAttribute.class);
if(j == ea.sort()) {
// 給對象的成員變量賦值
field.set(entity, covertAttrType(field, cell));
break;
}
}
}
}
// 將對象添加到集合中
list.add(entity);
}
} catch (Exception e) {
e.printStackTrace();
}
// 返回集合
return list;
}
/**
* 類型轉換 將cell 單元格格式轉爲 字段類型
*/
private Object covertAttrType(Field field, Cell cell) throws Exception {
String fieldType = field.getType().getSimpleName();
if ("String".equals(fieldType)) {
return getValue(cell);
}else if ("Date".equals(fieldType)) {
return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(getValue(cell)) ;
}else if ("int".equals(fieldType) || "Integer".equals(fieldType)) {
return Integer.parseInt(getValue(cell));
}else if ("double".equals(fieldType) || "Double".equals(fieldType)) {
return Double.parseDouble(getValue(cell));
}else {
return null;
}
}
/**
* 格式轉爲String
* @param cell
* @return
*/
public String getValue(Cell cell) {
if (cell == null) {
return "";
}
switch (cell.getCellType()) {
case STRING:
return cell.getRichStringCellValue().getString().trim();
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
Date dt = DateUtil.getJavaDate(cell.getNumericCellValue());
return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(dt);
} else {
// 防止數值變成科學計數法
String strCell = "";
Double num = cell.getNumericCellValue();
BigDecimal bd = new BigDecimal(num.toString());
if (bd != null) {
strCell = bd.toPlainString();
}
// 去除 浮點型 自動加的 .0
if (strCell.endsWith(".0")) {
strCell = strCell.substring(0, strCell.indexOf("."));
}
return strCell;
}
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
default:
return "";
}
}
// 省略 setters & getters ..................
}
3、報表導入和導出測試
/**
* 導出Excel,導出員工
*/
@RequestMapping(value = "/emp/export", method = RequestMethod.GET)
public void export() throws Exception {
// 1.構造數據
List<Employee> list = null; // 查詢數據庫獲得數據
// 2.加載模板數據
Resource resource = new ClassPathResource("excel.xlsx");
FileInputStream fis = new FileInputStream(resource.getFile());
// 3. 導出數據
new ExcelExportUtil(Employee.class,2,2).
export(response,fis,list,"員工報表.xlsx");
}
/**
* 導入Excel,添加員工
*/
@RequestMapping(value="/emp/import",method = RequestMethod.POST)
public Result importUser(@RequestParam(name="file") MultipartFile file) throws Exception {
// 1.解析Excel
List<Employee> list = new ExcelImportUtil(Employee.class).readExcel(file.getInputStream(),1,1)
// 2.批量保存員工
employeeService.saveAll(lis);
return new Result(ResultCode.SUCCESS);
}