一.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");
}
總結:
-
加載模板
-
重置模板靜態數據(大標題)
-
抽取模板樣式(數據行)
-
填充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功能如同名字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
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 入口類,用於構建開始各種操作