Java==POI報表詳解

一.POI報表

在企業開發中,Excel報表是一種最常見的報表需求,Excel報表開發一般分爲兩種形式:

(1)爲了方便操作,基於Excel的報表批量上傳數據

(2)通過java代碼,快速生成Excel報表

 

POI報表簡介:

Apache POI是Apache軟件基金會的開源項目,由Java編寫的免費開源的跨平臺的 Java API,Apache POI提供API給Java語言操作Microsoft Office的功能。

Excel分爲兩個大的版本Excel2003和Excel2007及以上兩個版本:

區別 Excel 2003 Excel 2007及以上
擴展名 xls xlsx
數據結構 二進制格式 xml格式
單sheet數據量 行:65535、列:256 行:1048576、列:16384
特點 存儲容量有限 基於xml壓縮,佔用空間小,操作效率高

API對象介紹

  • 工作簿(表): WorkBook (HssfWordBook:2003版本,XssfWorkBook:2007及以上)

  • 頁:Sheet

  • 行:Row

  • 單元格:Cell

二.快速入門

1.創建項目,環境搭建,導入pom.xml的座標文件

<dependencies>
    <!--2003版本-->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>4.0.1</version>
    </dependency>
    <!--2007及以上版本-->
    <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.通過java創建Excel

public class CreateDemo {


    /*
        通過java代碼創建excel
     */

    public static void main(String[] args)throws Exception {
        // 1.創建工作表
        /*
            HSSFWorkbook:2003版本
            XSSFWorkbook:2007及以上版本
            SXSSFWorkbook:處理百萬數據
         */
        Workbook wb = new XSSFWorkbook();
        // 2.創建第一頁
        Sheet sheet = wb.createSheet("poi入門");

        // 指定第三列寬度 24
        sheet.setColumnWidth(2, 24*256); // poi的設計者把我們設置的寬度 除以256

        // 3.創建第三行
        Row row = sheet.createRow(2);// 指定行索引行
        // 4.創建第三個單元格
        Cell cell = row.createCell(2);// 指定索引列
        // 5.填充數據
        cell.setCellValue("北京歡迎你");
        // 6.保存到本地
        FileOutputStream out = new FileOutputStream("d:/demo.xlsx");
        wb.write(out);
    }
}

3.通過java解析Excel

注意:excel單元格類型爲:

  • string

  • number

  • boolean

  • date

public class ParseDemo {
    /*
        通過java代碼解析excel
     */

    public static void main(String[] args) throws IOException {
        // 1.加載已有的excel工作表
        Workbook wb = new XSSFWorkbook("C:\\Users\\wsl\\Desktop\\demo.xlsx");
        // 2.獲取第一頁
        Sheet sheet = wb.getSheetAt(0);
        // 3.遍歷行
        // sheet.getLastRowNum(); 獲取最後一個有數據行的索引號(索引是從0開始),返回值4
        for (int i = 0; i < sheet.getLastRowNum() + 1; i++) {
            Row row = sheet.getRow(i);
            // 4.遍歷單元格
            // row.getLastCellNum(); 獲取最後一個有數據列的編號(編號是從1開始),返回值6
            for (int j = 2; j < row.getLastCellNum(); j++) {
                Cell cell = row.getCell(j);
                // 5.獲取單元格的數據
                // System.out.print(cell.getStringCellValue()+"   ");
                System.out.print(getCellValue(cell)+" ");

            }
            System.out.println("  ");// 換行
        }
    }


    //解析每個單元格的數據
    public static Object getCellValue(Cell cell) {
        Object obj = null;
        CellType cellType = cell.getCellType(); //獲取單元格數據類型
        switch (cellType) {
            case STRING: {
                obj = cell.getStringCellValue();//字符串
                break;
            }
            //excel默認將日期也理解爲數字
            case NUMERIC: {
                if (DateUtil.isCellDateFormatted(cell)) {
                    obj = cell.getDateCellValue();//日期
                } else {
                    obj = cell.getNumericCellValue(); // 數字
                }
                break;
            }
            case BOOLEAN: {
                obj = cell.getBooleanCellValue(); // 布爾
                break;
            }
            default: {
                break;
            }
        }
        return obj;
    }
}

三.案例:

按規定導出出貨表,導出出貨表的excel

自定義SQL語句:

-- 根據船期+企業id查詢
SELECT 
  c.`custom_name` AS customName,
  c.`contract_no` AS contractNo,
  cp.`product_no` AS productNo,
  cp.`cnumber`,
  cp.`factory_name` AS factoryName,
  c.`delivery_period` AS deliveryPeriod,
  c.`ship_time` AS shipTime,
  c.`trade_terms` AS tradeTerms
FROM `co_contract` c
	INNER JOIN `co_contract_product` cp ON c.`id` = cp.`contract_id`
	WHERE DATE_FORMAT(c.`ship_time`,'%Y-%m') = '2015-01' AND c.`company_id` = '1'

自定義VO對象

import java.io.Serializable;
import java.util.Date;

public class ContractProductVo implements Serializable {

	private String customName;		//客戶名稱
	private String contractNo;		//合同號,訂單號
	private String productNo;		//貨號
	private Integer cnumber;		//數量
	private String factoryName;		//廠家名稱,冗餘字段
	private Date deliveryPeriod;	//交貨期限
	private Date shipTime;			//船期
	private String tradeTerms;		//貿易條款

	public String getCustomName() {
		return customName;
	}

	public void setCustomName(String customName) {
		this.customName = customName;
	}

	public String getContractNo() {
		return contractNo;
	}

	public void setContractNo(String contractNo) {
		this.contractNo = contractNo;
	}

	public String getProductNo() {
		return productNo;
	}

	public void setProductNo(String productNo) {
		this.productNo = productNo;
	}

	public Integer getCnumber() {
		return cnumber;
	}

	public void setCnumber(Integer cnumber) {
		this.cnumber = cnumber;
	}

	public String getFactoryName() {
		return factoryName;
	}

	public void setFactoryName(String factoryName) {
		this.factoryName = factoryName;
	}

	public Date getDeliveryPeriod() {
		return deliveryPeriod;
	}

	public void setDeliveryPeriod(Date deliveryPeriod) {
		this.deliveryPeriod = deliveryPeriod;
	}

	public Date getShipTime() {
		return shipTime;
	}

	public void setShipTime(Date shipTime) {
		this.shipTime = shipTime;
	}

	public String getTradeTerms() {
		return tradeTerms;
	}

	public void setTradeTerms(String tradeTerms) {
		this.tradeTerms = tradeTerms;
	}

	@Override
	public String toString() {
		return "ContractProductVo{" +
				"customName='" + customName + '\'' +
				", contractNo='" + contractNo + '\'' +
				", productNo='" + productNo + '\'' +
				", cnumber=" + cnumber +
				", factoryName='" + factoryName + '\'' +
				", deliveryPeriod=" + deliveryPeriod +
				", shipTime=" + shipTime +
				", tradeTerms='" + tradeTerms + '\'' +
				'}';
	}
}

下載工具類:

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class DownloadUtils {
	
	/**
	 * @param filePath 要下載的文件路徑
	 * @param returnName 返回的文件名
	 * @param response HttpServletResponse
	 * @param delFlag 是否刪除文件
	 */
	protected static void download(String filePath,String returnName,HttpServletResponse response,boolean delFlag){
		prototypeDownload(new File(filePath), returnName, response, delFlag);
	}


	/**
	 * @param file 要下載的文件
	 * @param returnName 返回的文件名
	 * @param response HttpServletResponse
	 * @param delFlag 是否刪除文件
	 */
	protected static void download(File file,String returnName,HttpServletResponse response,boolean delFlag){
		prototypeDownload(file, returnName, response, delFlag);
	}
	
	/**
	 * @param file 要下載的文件
	 * @param returnName 返回的文件名
	 * @param response HttpServletResponse
	 * @param delFlag 是否刪除文件
	 */
	public static void prototypeDownload(File file,String returnName,HttpServletResponse response,boolean delFlag){
		// 下載文件
		FileInputStream inputStream = null;
		ServletOutputStream outputStream = null;
		try {
			if(!file.exists()) return;
			response.reset();
			//設置響應類型	PDF文件爲"application/pdf",WORD文件爲:"application/msword", EXCEL文件爲:"application/vnd.ms-excel"。  
			response.setContentType("application/octet-stream;charset=utf-8");

			//設置響應的文件名稱,並轉換成中文編碼
			//returnName = URLEncoder.encode(returnName,"UTF-8");
			returnName = response.encodeURL(new String(returnName.getBytes(),"iso8859-1"));	//保存的文件名,必須和頁面編碼一致,否則亂碼
			
			//attachment作爲附件下載;inline客戶端機器有安裝匹配程序,則直接打開;注意改變配置,清除緩存,否則可能不能看到效果
			response.addHeader("Content-Disposition",   "attachment;filename="+returnName);  
			
			//將文件讀入響應流
			inputStream = new FileInputStream(file);
			outputStream = response.getOutputStream();
			int length = 1024;
			int readLength=0;
			byte buf[] = new byte[1024];
			readLength = inputStream.read(buf, 0, length);
			while (readLength != -1) {
				outputStream.write(buf, 0, readLength);
				readLength = inputStream.read(buf, 0, length);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				outputStream.flush();
			} catch (IOException e) {
				e.printStackTrace();
			}
			try {
				outputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			try {
				inputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			//刪除原文件
			
			if(delFlag) {				
				file.delete();
			}
		}
	}

	/**
	 * @param byteArrayOutputStream 將文件內容寫入ByteArrayOutputStream
	 * @param response HttpServletResponse	寫入response
	 * @param returnName 返回的文件名
	 */
	public static void download(ByteArrayOutputStream byteArrayOutputStream, HttpServletResponse response, String returnName) throws IOException{
		response.setContentType("application/octet-stream;charset=utf-8");
		returnName = response.encodeURL(new String(returnName.getBytes(),"iso8859-1"));			//保存的文件名,必須和頁面編碼一致,否則亂碼
		response.addHeader("Content-Disposition",   "attachment;filename=" + returnName);  
		response.setContentLength(byteArrayOutputStream.size());
		
		ServletOutputStream outputstream = response.getOutputStream();	//取得輸出流
		byteArrayOutputStream.writeTo(outputstream);					//寫到輸出流
		byteArrayOutputStream.close();									//關閉
		outputstream.flush();											//刷數據
	}
}

基礎數據:

@Controller
@RequestMapping(value = "/cargo/contract", name = "出貨表導出")
public class OutContractController extends BaseController {

    @Reference // alibaba
    private ContractService contractService;

    @RequestMapping(value = "/print", name = "跳轉到查詢頁面")
    public String print() {
        return "cargo/print/contract-print";

    }

    @RequestMapping(value = "/printExcel", name = "出貨表excel下載")
    public void printExcel(String shipTime) {
        //i.查詢vo數據集合
        List<ContractProductVo> list = contractService.findByShipTime(shipTime,getCompanyId());
        System.out.println(list.size());
        //ii.將vo對象封裝到excel文件中

        //iii.完成文件下載
    }
}

ContractService

// 根據船期查詢出貨表
@Override
public List<ContractProductVo> findByShipTime(String shipTime, String companyId) {
    return contractDao.findByShipTime(shipTime,companyId);
}

ContractDao

// 根據船期查詢出貨表
List<ContractProductVo> findByShipTime(@Param("shipTime") String shipTime, @Param("companyId") String companyId);
<!--根據船期查詢出貨表-->
<select id="findByShipTime" resultType="cn.wsl.domain.vo.ContractProductVo">
    SELECT
      c.`custom_name` AS customName,
      c.`contract_no` AS contractNo,
      cp.`product_no` AS productNo,
      cp.`cnumber`,
      cp.`factory_name` AS factoryName,
      c.`delivery_period` AS deliveryPeriod,
      c.`ship_time` AS shipTime,
      c.`trade_terms` AS tradeTerms
    FROM `co_contract` c
        INNER JOIN `co_contract_product` cp ON c.`id` = cp.`contract_id`
        WHERE DATE_FORMAT(c.`ship_time`,'%Y-%m') = #{shipTime} AND c.`company_id` = #{companyId}
</select>

 

導出樣式:

Excel下載:

(1)不帶樣式:

@Controller
@RequestMapping(value = "/cargo/contract", name = "出貨表導出")
public class OutContractController extends BaseController {

    @Reference // alibaba
    private ContractService contractService;

    @RequestMapping(value = "/print", name = "跳轉到查詢頁面")
    public String print() {
        return "cargo/print/contract-print";

    }

    @RequestMapping(value = "/printExcel", name = "出貨表excel下載")
    public void printExcel(String shipTime) throws Exception {
        //i.查詢vo數據集合
        List<ContractProductVo> list = contractService.findByShipTime(shipTime, getCompanyId());
        System.out.println(list.size());
        //ii.將vo對象封裝到excel文件中
        // 1.創建Workbook 2007版本
        Workbook wb = new XSSFWorkbook();
        // 2.創建第一頁
        Sheet sheet = wb.createSheet("Sheet1");

        // 指定列寬度
        sheet.setColumnWidth(1, 20 * 256);
        sheet.setColumnWidth(2, 15 * 256);
        sheet.setColumnWidth(3, 15 * 256);
        sheet.setColumnWidth(4, 15 * 256);
        sheet.setColumnWidth(5, 15 * 256);
        sheet.setColumnWidth(6, 15 * 256);
        sheet.setColumnWidth(7, 15 * 256);

        // 合併單元格 行開始、行結束、列開始、列結束 :取值都是索引
        sheet.addMergedRegion(new CellRangeAddress(0, 0, 1, 8));

        // 3.第一行指定大標題
        // 3.1 創建第一行
        Row row = sheet.createRow(0);
        row.setHeightInPoints(36);// 行高
        // 3.2 創建第二個單元格
        Cell cell = row.createCell(1);
        // 3.3 填充大標題
        String bigTile = shipTime.replace("-", "年").replace("年0", "年") + "月份出貨表";
        cell.setCellValue(bigTile);
        // 4.第二行指定列標題
        // 4.1 創建第二行
        row = sheet.createRow(1);
        row.setHeightInPoints(26);// 行高
        // 4.2 指定列標題數組
        String[] colTitile = new String[]{"", "客戶", "合同號", "貨號", "數量", "工廠", "工廠交期", "船期", "貿易條款"};
        // 4.3 遍歷數組設置列標題
        for (int i = 1; i < colTitile.length; i++) {
            cell = row.createCell(i);
            cell.setCellValue(colTitile[i]);
        }
        // 5.從第三行開始,遍歷vo數據集合,填充數據行
        int index = 2;
        for (ContractProductVo vo : list) {
            row = sheet.createRow(index);
            row.setHeightInPoints(24);// 行高
            // 客戶單元格
            cell = row.createCell(1);
            cell.setCellValue(vo.getCustomName());
            // 合同號單元格
            cell = row.createCell(2);
            cell.setCellValue(vo.getContractNo());
            // 貨號單元格
            cell = row.createCell(3);
            cell.setCellValue(vo.getProductNo());
            // 數量單元格
            cell = row.createCell(4);
            cell.setCellValue(vo.getCnumber());
            // 工廠單元格
            cell = row.createCell(5);
            cell.setCellValue(vo.getFactoryName());
            // 工廠交期單元格
            cell = row.createCell(6);
            cell.setCellValue(new SimpleDateFormat("yyyy-MM-dd").format(vo.getDeliveryPeriod()));
            // 船期單元格
            cell = row.createCell(7);
            cell.setCellValue(new SimpleDateFormat("yyyy-MM-dd").format(vo.getShipTime()));
            // 貿易條款單元格
            cell = row.createCell(8);
            cell.setCellValue(vo.getTradeTerms());
            index++;
        }

        //iii.完成文件下載
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        // 將workbook的內容輸出到字節數組輸出中
        wb.write(byteArrayOutputStream);
        DownloadUtils.download(byteArrayOutputStream, response, bigTile + ".xlsx");
    }
}

(2)帶樣式

@Controller
@RequestMapping(value = "/cargo/contract", name = "出貨表導出")
public class OutContractController extends BaseController {

    @Reference // alibaba
    private ContractService contractService;

    @RequestMapping(value = "/print", name = "跳轉到查詢頁面")
    public String print() {
        return "cargo/print/contract-print";

    }

    @RequestMapping(value = "/printExcel", name = "出貨表excel下載")
    public void printExcel(String shipTime) throws Exception {
        //i.查詢vo數據集合
        List<ContractProductVo> list = contractService.findByShipTime(shipTime, getCompanyId());
        System.out.println(list.size());
        //ii.將vo對象封裝到excel文件中
        // 1.創建Workbook 2007版本
        Workbook wb = new XSSFWorkbook();
        // 2.創建第一頁
        Sheet sheet = wb.createSheet("Sheet1");

        // 指定列寬度
        sheet.setColumnWidth(1, 20 * 256);
        sheet.setColumnWidth(2, 15 * 256);
        sheet.setColumnWidth(3, 15 * 256);
        sheet.setColumnWidth(4, 15 * 256);
        sheet.setColumnWidth(5, 15 * 256);
        sheet.setColumnWidth(6, 15 * 256);
        sheet.setColumnWidth(7, 15 * 256);

        // 合併單元格 行開始、行結束、列開始、列結束 :取值都是索引
        sheet.addMergedRegion(new CellRangeAddress(0, 0, 1, 8));

        // 3.第一行指定大標題
        // 3.1 創建第一行
        Row row = sheet.createRow(0);
        row.setHeightInPoints(36);// 行高
        // 3.2 創建第二個單元格
        Cell cell = row.createCell(1);
        // 3.3 填充大標題
        String bigTile = shipTime.replace("-", "年").replace("年0", "年") + "月份出貨表";
        cell.setCellValue(bigTile);

        cell.setCellStyle(bigTitle(wb)); // 大標題樣式
        // 4.第二行指定列標題
        // 4.1 創建第二行
        row = sheet.createRow(1);
        row.setHeightInPoints(26);// 行高
        // 4.2 指定列標題數組
        String[] colTitile = new String[]{"", "客戶", "合同號", "貨號", "數量", "工廠", "工廠交期", "船期", "貿易條款"};
        // 4.3 遍歷數組設置列標題
        for (int i = 1; i < colTitile.length; i++) {
            cell = row.createCell(i);
            cell.setCellValue(colTitile[i]);
            cell.setCellStyle(title(wb)); // 列標題樣式
        }
        // 5.從第三行開始,遍歷vo數據集合,填充數據行
        int index = 2;
        for (ContractProductVo vo : list) {
            row = sheet.createRow(index);
            row.setHeightInPoints(24);// 行高
            // 客戶單元格
            cell = row.createCell(1);
            cell.setCellValue(vo.getCustomName());
            cell.setCellStyle(text(wb)); // 數據樣式
            // 合同號單元格
            cell = row.createCell(2);
            cell.setCellValue(vo.getContractNo());
            cell.setCellStyle(text(wb)); // 數據樣式
            // 貨號單元格
            cell = row.createCell(3);
            cell.setCellValue(vo.getProductNo());
            cell.setCellStyle(text(wb)); // 數據樣式
            // 數量單元格
            cell = row.createCell(4);
            cell.setCellValue(vo.getCnumber());
            cell.setCellStyle(text(wb)); // 數據樣式
            // 工廠單元格
            cell = row.createCell(5);
            cell.setCellValue(vo.getFactoryName());
            cell.setCellStyle(text(wb)); // 數據樣式
            // 工廠交期單元格
            cell = row.createCell(6);
            cell.setCellValue(new SimpleDateFormat("yyyy-MM-dd").format(vo.getDeliveryPeriod()));
            cell.setCellStyle(text(wb)); // 數據樣式
            // 船期單元格
            cell = row.createCell(7);
            cell.setCellValue(new SimpleDateFormat("yyyy-MM-dd").format(vo.getShipTime()));
            cell.setCellStyle(text(wb)); // 數據樣式
            // 貿易條款單元格
            cell = row.createCell(8);
            cell.setCellValue(vo.getTradeTerms());
            cell.setCellStyle(text(wb)); // 數據樣式
            index++;
        }

        //iii.完成文件下載
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        // 將workbook的內容輸出到字節數組輸出中
        wb.write(byteArrayOutputStream);
        DownloadUtils.download(byteArrayOutputStream, response, bigTile + ".xlsx");
    }


    //大標題的樣式
    public CellStyle bigTitle(Workbook wb){
        CellStyle style = wb.createCellStyle();
        Font font = wb.createFont();
        font.setFontName("宋體");
        font.setFontHeightInPoints((short)16);
        font.setBold(true);//字體加粗
        style.setFont(font);
        style.setAlignment(HorizontalAlignment.CENTER);				//橫向居中
        style.setVerticalAlignment(VerticalAlignment.CENTER);		//縱向居中
        return style;
    }

    //小標題的樣式
    public CellStyle title(Workbook wb){
        CellStyle style = wb.createCellStyle();
        Font font = wb.createFont();
        font.setFontName("黑體");
        font.setFontHeightInPoints((short)12);
        style.setFont(font);
        style.setAlignment(HorizontalAlignment.CENTER);				//橫向居中
        style.setVerticalAlignment(VerticalAlignment.CENTER);		//縱向居中
        style.setBorderTop(BorderStyle.THIN);						//上細線
        style.setBorderBottom(BorderStyle.THIN);					//下細線
        style.setBorderLeft(BorderStyle.THIN);						//左細線
        style.setBorderRight(BorderStyle.THIN);						//右細線
        return style;
    }

    //文字樣式
    public CellStyle text(Workbook wb){
        CellStyle style = wb.createCellStyle();
        Font font = wb.createFont();
        font.setFontName("Times New Roman");
        font.setFontHeightInPoints((short)10);

        style.setFont(font);

        style.setAlignment(HorizontalAlignment.LEFT);				//橫向居左
        style.setVerticalAlignment(VerticalAlignment.CENTER);		//縱向居中
        style.setBorderTop(BorderStyle.THIN);						//上細線
        style.setBorderBottom(BorderStyle.THIN);					//下細線
        style.setBorderLeft(BorderStyle.THIN);						//左細線
        style.setBorderRight(BorderStyle.THIN);						//右細線

        return style;
    }
}

(3)使用模板導出

自定義生成Excel報表文件還是有很多不盡如意的地方,特別是針對複雜報表頭,單元格樣式,字體等操作。手寫這些代碼不僅費時費力,有時候效果還不太理想。那怎麼樣才能更方便的對報表樣式,報表頭進行處理呢?

答案:使用已經準備好的Excel模板,開發者只需要關注模板中的數據即可。

提取模板:

模板打印

// 模板下載
@RequestMapping(value = "/printExcelTemplate", name = "出貨表excel模板下載")
public void printExcelTemplate(String shipTime) throws Exception {
    // i.查詢vo數據集合
    List<ContractProductVo> list = contractService.findByShipTime(shipTime, getCompanyId());
    // ii.加載模板,填充數據,生成excel
    // 1.獲取模板真實路徑
    String realPath = session.getServletContext().getRealPath("/make/xlsprint/tOUTPRODUCT.xlsx");
    // 2.基於模板創建workbook
    Workbook wb = new XSSFWorkbook(realPath);
    // 3.獲取第一頁
    Sheet sheet = wb.getSheetAt(0);
    // 4.重置大標題數據
    Row row = sheet.getRow(0);
    Cell cell = row.getCell(1);
    String bigTile = shipTime.replace("-", "年").replace("年0", "年") + "月份出貨表";
    cell.setCellValue(bigTile);
    // 5.列標題(略)
    // 6.抽取數據行的樣式
    // 6.1 獲取第三行對象
    row = sheet.getRow(2);
    // 6.2 定義樣式存儲數組
    CellStyle[] css = new CellStyle[9]; // null,客人,合同號,貨號
    // 6.3 遍歷第三行
    for (int i = 1; i < row.getLastCellNum(); i++) {
        // 獲取單元格對象
        cell = row.getCell(i);
        // 獲取樣式,並設置到數組
        css[i] = cell.getCellStyle();
    }
    // 7.遍歷vo數據集合,填充數據
    int index = 2;
    for (ContractProductVo vo : list) {
        row = sheet.createRow(index);
        // 客戶單元格
        cell = row.createCell(1);
        cell.setCellValue(vo.getCustomName());
        cell.setCellStyle(css[1]); // 數據樣式
        // 合同號單元格
        cell = row.createCell(2);
        cell.setCellValue(vo.getContractNo());
        cell.setCellStyle(css[2]); // 數據樣式
        // 貨號單元格
        cell = row.createCell(3);
        cell.setCellValue(vo.getProductNo());
        cell.setCellStyle(css[3]); // 數據樣式
        // 數量單元格
        cell = row.createCell(4);
        cell.setCellValue(vo.getCnumber());
        cell.setCellStyle(css[4]); // 數據樣式
        // 工廠單元格
        cell = row.createCell(5);
        cell.setCellValue(vo.getFactoryName());
        cell.setCellStyle(css[5]); // 數據樣式
        // 工廠交期單元格
        cell = row.createCell(6);
        cell.setCellValue(new SimpleDateFormat("yyyy-MM-dd").format(vo.getDeliveryPeriod()));
        cell.setCellStyle(css[6]); // 數據樣式
        // 船期單元格
        cell = row.createCell(7);
        cell.setCellValue(new SimpleDateFormat("yyyy-MM-dd").format(vo.getShipTime()));
        cell.setCellStyle(css[7]); // 數據樣式
        // 貿易條款單元格
        cell = row.createCell(8);
        cell.setCellValue(vo.getTradeTerms());
        cell.setCellStyle(css[8]); // 數據樣式
        index++;
    }

    //iii.完成文件下載
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    // 將workbook的內容輸出到字節數組輸出中
    wb.write(byteArrayOutputStream);
    DownloadUtils.download(byteArrayOutputStream, response, bigTile + ".xlsx");
}

總結:

  1. 加載模板

  2. 重置模板靜態數據(大標題)

  3. 抽取模板樣式(數據行)

  4. 填充vo數據

 

四.百萬數據Excel

(1)概述

在真實的環境下,我們導出導入的數據量在幾十萬甚至上百萬條,Excel 2007雖然可以支持1048576條數據存儲;但實際運行時還可能存在問題,原因是執行POI報表所產生的行對象,單元格對象,字體對象,他們都不會銷燬,這就導致OOM(內存溢出)的風險。

JDK性能監控工具

這裏我們使用JDK提供的性能工具Jvisualvm來監視程序的運行情況,包括CPU,垃圾回收,內存的分配和使用情況,這讓程序的運行階段變得更加可控。

Jvisualvm工具位於JAVA_HOME/bin目錄下,直接雙擊就可以打開該程序。

 

 

百萬數據報表導出

SxssfWorkBookx對象:是用來生成海量excel數據文件,主要原理是藉助臨時存儲空間生成excel(當內存中的對象數量到達一定數量後,寫入到臨時文件,銷燬內存對象,直至Excel導出完成)

缺點

  • 不能使用模板

  • 樣式對象不能超過64000個

注意:

比較:

使用:XSSFWorkbook對象,造成內存一致增長

使用:SXSSFWorkbook對象,藉助臨時文件,內存有增有減

 

(2)百萬數據報表導入

**POI針對Excel文件解析提供了兩種方式:**

用戶模式:通過POI提供的基礎方法完成excel解析,操作簡單,但創建太多的對象,消耗內存

事件模式:基於SAX方式解析XML,它逐行掃描文檔,一邊掃描,一邊解析。

 

通過用戶模式解析:即Dom方式

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.awt.*;
import java.io.IOException;

public class ParseDemo {
    /*
        通過java代碼解析excel
     */

    public static void main(String[] args) throws IOException {
        // 1.加載已有的excel工作表
        Workbook wb = new XSSFWorkbook("C:\\\\Users\\\\Desktop\\\\2020年2月份出貨表.xlsx");
        // 2.獲取第一頁
        Sheet sheet = wb.getSheetAt(0);
        // 3.遍歷行
        // sheet.getLastRowNum(); 獲取最後一個有數據行的索引號(索引是從0開始),返回值4
        for (int i = 0; i < sheet.getLastRowNum() + 1; i++) {
            Row row = sheet.getRow(i);
            // 4.遍歷單元格
            // row.getLastCellNum(); 獲取最後一個有數據列的編號(編號是從1開始),返回值6
            for (int j = 2; j < row.getLastCellNum(); j++) {
                Cell cell = row.getCell(j);
                // 5.獲取單元格的數據
                // System.out.print(cell.getStringCellValue()+"   ");
                System.out.print(getCellValue(cell)+" ");

            }
            System.out.println("  ");// 換行
        }
    }


    //解析每個單元格的數據
    public static Object getCellValue(Cell cell) {
        Object obj = null;
        CellType cellType = cell.getCellType(); //獲取單元格數據類型
        switch (cellType) {
            case STRING: {
                obj = cell.getStringCellValue();//字符串
                break;
            }
            //excel默認將日期也理解爲數字
            case NUMERIC: {
                if (DateUtil.isCellDateFormatted(cell)) {
                    obj = cell.getDateCellValue();//日期
                } else {
                    obj = cell.getNumericCellValue(); // 數字
                }
                break;
            }
            case BOOLEAN: {
                obj = cell.getBooleanCellValue(); // 布爾
                break;
            }
            default: {
                break;
            }
        }
        return obj;
    }
}

通過SAX方式解析:重要:

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackageAccess;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.model.StylesTable;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

import java.io.InputStream;

public class ExcelParse {

    public static void parse(String path) throws Exception {
        //解析器
        SheetHandler hl = new SheetHandler();
        //1.根據 Excel 獲取 OPCPackage 對象
        OPCPackage pkg = OPCPackage.open(path, PackageAccess.READ);
        try {
            //2.創建 XSSFReader 對象
            XSSFReader reader = new XSSFReader(pkg);
            //3.獲取 SharedStringsTable 對象
            SharedStringsTable sst = reader.getSharedStringsTable();
            //4.獲取 StylesTable 對象
            StylesTable styles = reader.getStylesTable();
            XMLReader parser = XMLReaderFactory.createXMLReader();
            // 處理公共屬性
            parser.setContentHandler(new XSSFSheetXMLHandler(styles, sst, hl,
                    false));
            XSSFReader.SheetIterator sheets = (XSSFReader.SheetIterator)
                    reader.getSheetsData();

            //逐行讀取逐行解析
            while (sheets.hasNext()) {
                InputStream sheetstream = sheets.next();
                InputSource sheetSource = new InputSource(sheetstream);
                try {
                    parser.parse(sheetSource);
                } finally {
                    sheetstream.close();
                }
            }
        } finally {
            pkg.close();
        }
    }

    public static void main(String[] args) throws Exception {
        parse("C:\\Users\\Desktop\\2020年2月份出貨表.xlsx");
    }
}
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
import org.apache.poi.xssf.usermodel.XSSFComment;

public class SheetHandler implements XSSFSheetXMLHandler.SheetContentsHandler {

    // 行解析開始
    @Override
    public void startRow(int rowNum) {
        if (rowNum > 2) {
            System.out.println("開啓數據解析");
        }
    }

    // 行解析結束
    @Override
    public void endRow(int rowNum) {
        System.out.println(""); // 手動換行
    }

    // 獲取單元格數據
    /*
        cellReference:單元格編號(C3、D5、E8),可以判斷數據的類型,方便取值
        formattedValue:單元格數據(內容)
        comment:單元格批註(註釋)

     */
    @Override
    public void cell(String cellReference, String formattedValue, XSSFComment comment) {
        System.out.print(formattedValue+"  ");
    }
}

pom.xml

    <dependencies>
        <!--2003版本-->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>4.0.1</version>
        </dependency>
        <!--2007及以上版本-->
        <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>

    <build>
        <plugins>
            <!-- 設置編譯版本爲1.8 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

 

資源擴展:

(1)EasyPOI

EasyPOI

easypoi功能如同名字easy,主打的功能就是容易,讓一個沒見接觸過poi的人員,就可以方便的寫出Excel導出,Excel模板導出,Excel導入,Word模板導出,通過簡單的註解和模板語言(熟悉的表達式語法),完成以前複雜的寫法。

入門案例:

<dependency>
    <groupId>cn.afterturn</groupId>
    <artifactId>easypoi-base</artifactId>
    <version>3.2.0</version>
</dependency>
<dependency>
    <groupId>cn.afterturn</groupId>
    <artifactId>easypoi-web</artifactId>
    <version>3.2.0</version>
</dependency>
<dependency>
    <groupId>cn.afterturn</groupId>
    <artifactId>easypoi-annotation</artifactId>
    <version>3.2.0</version>
</dependency>

定義實體

public class ContractProductVo implements Serializable {

    @Excel(name = "客戶")
    private String customName;        //客戶名稱
    @Excel(name = "合同號")
    private String contractNo;        //合同號,訂單號
    @Excel(name = "貨號")
    private String productNo;        //貨號
    @Excel(name = "數量",type = 10)
    private Integer cnumber;        //數量
    @Excel(name = "工廠")
    private String factoryName;        //廠家名稱
    @Excel(name = "工廠交期", format = "yyyy-MM-dd",width = 15)
    private Date deliveryPeriod;    //交貨期限
    @Excel(name = "船期", format = "yyyy-MM-dd",width = 15)
    private Date shipTime;            //船期
    @Excel(name = "貿易條款")
    private String tradeTerms;        //貿易條款

    public String getCustomName() {
        return customName;
    }

    public void setCustomName(String customName) {
        this.customName = customName;
    }

    public String getContractNo() {
        return contractNo;
    }

    public void setContractNo(String contractNo) {
        this.contractNo = contractNo;
    }

    public String getProductNo() {
        return productNo;
    }

    public void setProductNo(String productNo) {
        this.productNo = productNo;
    }

    public Integer getCnumber() {
        return cnumber;
    }

    public void setCnumber(Integer cnumber) {
        this.cnumber = cnumber;
    }

    public String getFactoryName() {
        return factoryName;
    }

    public void setFactoryName(String factoryName) {
        this.factoryName = factoryName;
    }

    public Date getDeliveryPeriod() {
        return deliveryPeriod;
    }

    public void setDeliveryPeriod(Date deliveryPeriod) {
        this.deliveryPeriod = deliveryPeriod;
    }

    public Date getShipTime() {
        return shipTime;
    }

    public void setShipTime(Date shipTime) {
        this.shipTime = shipTime;
    }

    public String getTradeTerms() {
        return tradeTerms;
    }

    public void setTradeTerms(String tradeTerms) {
        this.tradeTerms = tradeTerms;
    }

    @Override
    public String toString() {
        return "ContractProductVo{" +
                "customName='" + customName + '\'' +
                ", contractNo='" + contractNo + '\'' +
                ", productNo='" + productNo + '\'' +
                ", cnumber=" + cnumber +
                ", factoryName='" + factoryName + '\'' +
                ", deliveryPeriod=" + deliveryPeriod +
                ", shipTime=" + shipTime +
                ", tradeTerms='" + tradeTerms + '\'' +
                '}';
    }
}

創建excel

public class CreateExcel {

    public static void main(String[] args)throws Exception {
        // 1.定義excel配置
        ExportParams exportParams = new ExportParams();
        exportParams.setSheetName("Sheet1");// 頁名稱
        exportParams.setTitle("2015年01月份出貨表");// 大標題
        exportParams.setType(ExcelType.XSSF);// 07及以上版本
        // 2.創建workbook對象
        Workbook wb = ExcelExportUtil.exportExcel(exportParams, ContractProductVo.class, getData());
        // 3.導出到文件
        FileOutputStream out = new FileOutputStream("d:/demo.xlsx");
        wb.write(out);
        wb.close();
    }

    //從數據庫中查找了數據
    public static List<ContractProductVo> getData() {
        List<ContractProductVo> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            ContractProductVo vo = new ContractProductVo();
            vo.setCustomName("客戶"+i);
            vo.setContractNo("合同號"+i);
            vo.setProductNo("貨號"+i);
            vo.setCnumber(i);
            vo.setFactoryName("工廠"+i);
            vo.setDeliveryPeriod(new Date());
            vo.setShipTime(new Date());
            vo.setTradeTerms("條款"+i);
            list.add(vo);
        }
        return list;
    }
}

解析Excel

public class ParseExcel {

    public static void main(String[] args) {
        // 1.解析excel的配置參數
        ImportParams params = new ImportParams();
        params.setTitleRows(1); // 大標題行數
        params.setHeadRows(1); //  列標題行數

        // 2.實現解析
        List<ContractProductVo> list = ExcelImportUtil.importExcel(new File("D:/demo.xlsx"), ContractProductVo.class, params);

        // 3.遍歷輸出
        for (ContractProductVo contractProductVo : list) {
            System.out.println(contractProductVo);
        }
    }
}

(2)EasyExcel

easyExcel

JAVA解析Excel工具EasyExcel

Java解析、生成Excel比較有名的框架有Apache poi、jxl。但他們都存在一個嚴重的問題就是非常的耗內存,poi有一套SAX模式的API可以一定程度的解決一些內存溢出的問題,但POI還是有一些缺陷,比如07版Excel解壓縮以及解壓後存儲都是在內存中完成的,內存消耗依然很大。easyexcel重寫了poi對07版Excel的解析,能夠原本一個3M的excel用POI sax依然需要100M左右內存降低到幾M,並且再大的excel不會出現內存溢出,03版依賴POI的sax模式。在上層做了模型轉換的封裝,讓使用者更加簡單方便

EasyExcel文檔

EasyExcel 入口類,用於構建開始各種操作

 

 

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