Apache POI 實現報表導入和導出

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);
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章