隨時隨地閱讀更多技術實戰乾貨,獲取項目源碼、學習資料,請關注源代碼社區公衆號(ydmsq666)、博主微信(guyun297890152)、QQ技術交流羣(183198395)。
在之前的一篇文章java操作Excel實戰乾貨中展示了使用poi庫讀取excel表格的的用法,今天演示另一個常用功能,將數據導出到excel中,按照實戰演練方式,後臺提供接口,數據來源於數據庫,然後導出excel輸出到前端,詳細如下:
接口代碼:
@RequestMapping(value = "/test/excel/export", method = { RequestMethod.POST })
public ModelAndView exportExcel(@RequestBody RequestBean req) {
// 查詢數據
List<Student> list = testService.getStudent(req);
try {
Map<String, Object> model = new HashMap<String, Object>();
// 表格名
model.put("sheetName", "學生表");
// 文件名
model.put("filename", "學生統計導出-" + new SimpleDateFormat("yyyyMMddhhmmss").format(new Date()) + ".xls");
// 設置需要導出的數據
model.put("items", list);
// 設置需要顯示的表頭
model.put("showName", new String[] { "學號", "姓名", "性別" });
// 設置表頭對應的屬性
model.put("fieldName", new String[] { "number", "name", "sex" });
return new ModelAndView(new PoiExcelView(), model);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("導出excel出錯,請檢查;異常爲:" + e.getMessage());
}
}
上面有詳細註釋,主要設置表格的一些屬性,以及查詢需要導出的實際內容,service簡單介紹:
public List<Student> getStudent(RequestBean req) {
// 爲了方便測試直接製造數據,實際中應該從數據庫中讀取
List<Student> list = new ArrayList<Student>();
list.add(new Student("001", "張三", "男"));
list.add(new Student("002", "李四", "男"));
list.add(new Student("003", "小娟", "女"));
return list;
}
這裏爲了方便測試,直接寫死的數據。
測試用的實體對象Student就三個屬性,這裏就列代碼了。
excel核心控件:PoiExcelView:
package com.home.javaee.view;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.springframework.web.servlet.view.document.AbstractXlsView;
import com.home.base.data.CollectionUtil;
import com.home.base.data.StringUtil;
import com.home.base.reflect.ReflectUtil;
/**
* Excel控件
*
* @author fengjian
*
*/
public class PoiExcelView extends AbstractXlsView {
private static Logger logger = Logger.getLogger(PoiExcelView.class);
@Override
protected void buildExcelDocument(Map<String, Object> model, Workbook arg1, HttpServletRequest request,
HttpServletResponse response) throws Exception {
OutputStream os = null;
String sheetName = "";
try {
// 獲取表格名
sheetName = (String) model.get("sheetName");
// 獲取文件名
String filename = (String) model.get("filename");
// 獲取顯示錶頭
String[] showName = (String[]) model.get("showName");
// 獲取表頭對應屬性
String[] fieldName = (String[]) model.get("fieldName");
// 獲取列表數據
List<?> list = (List<?>) model.get("items");
response.setContentType("APPLICATION/OCTET-STREAM");
String fileNameURL = URLEncoder.encode(filename, "UTF-8");
// 設置響應header
response.setHeader("Content-Disposition",
"attachment; filename=" + fileNameURL + ";filename*=utf-8''" + fileNameURL);
// 獲取輸入流
os = response.getOutputStream();
HSSFWorkbook workbook = (HSSFWorkbook) arg1;
// 創建表格
HSSFSheet sheet = workbook.createSheet(sheetName);
// 設置表格總標題和每列的表頭以及樣式
addTitle(sheet, showName, getHeaderStyle(workbook), getContentStyle(workbook));
// 設置表格內容以及樣式
addContent(sheet, list, fieldName, getContentStyle(workbook), false, null);
// 輸出表格
workbook.write(os);
} catch (Throwable e) {
logger.error(sheetName + "導出出錯:" + e.getMessage(), e);
} finally {
try {
os.flush();
os.close();
} catch (Throwable e) {
logger.error(sheetName + "導出Excel出錯:" + e.getMessage(), e);
}
}
}
/**
* 獲取標題欄樣式配置
*
* @param workbook
* @return
*/
public static HSSFCellStyle getHeaderStyle(HSSFWorkbook workbook) {
HSSFCellStyle format = workbook.createCellStyle();
// 創建字體
HSSFFont font = workbook.createFont();
// 加粗
font.setBold(true);
// 黑體
font.setFontName("黑體");
// 字體大小
font.setFontHeightInPoints((short) 16);
// 垂直居中
format.setVerticalAlignment(VerticalAlignment.CENTER);
// 水平居中
format.setAlignment(HorizontalAlignment.CENTER);
// 設置字體
format.setFont(font);
return format;
}
/**
* 獲取內容樣式配置
*
* @param workbook
* @return
*/
public static HSSFCellStyle getContentStyle(HSSFWorkbook workbook) {
HSSFCellStyle format = workbook.createCellStyle();
// 創建字體
HSSFFont font = workbook.createFont();
// 宋體
font.setFontName("宋體");
// 垂直居中
format.setVerticalAlignment(VerticalAlignment.CENTER);
// 水平居中
format.setAlignment(HorizontalAlignment.CENTER);
// 設置字體
format.setFont(font);
// 下邊框樣式
format.setBorderBottom(BorderStyle.THIN);
// 左邊框樣式
format.setBorderLeft(BorderStyle.THIN);
// 上邊框樣式
format.setBorderTop(BorderStyle.THIN);
// 右邊框樣式
format.setBorderRight(BorderStyle.THIN);
return format;
}
/**
* 給表格設置每列的標題,格式:表格第一行合併居中用表格名稱作爲整個表格的標題,第二行才作爲列標題,第三行開始爲實際內容
*
* @param sheet
* @param titleNames
* 列標題數組
* @param headerStyle
* 表格標題樣式
* @param contentStyle
* 內容樣式
*/
public static void addTitle(HSSFSheet sheet, String[] titleNames, HSSFCellStyle headerStyle,
HSSFCellStyle contentStyle) {
// 合併單元格,把第一行按照數據的列寬進行合併,然後用表格名稱作爲標題,比如'員工工資表'
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, titleNames.length - 1));
// 取出第一行
HSSFRow row = sheet.createRow(0);
// 第一格也就是合併後的整個單元格
HSSFCell cell = row.createCell(0);
// 用表格名作爲主標題
cell.setCellValue(sheet.getSheetName());
// 設置標題樣式
cell.setCellStyle(headerStyle);
// 用第二行作爲每列數據的列標題
row = sheet.createRow(1);
// 循環填充列標題,並設置樣式
for (int i = 0; i < titleNames.length; i++) {
cell = row.createCell(i);
cell.setCellValue(titleNames[i]);
cell.setCellStyle(contentStyle);
}
}
/**
* 添加表格內容,從指定行開始
*
* @param sheet
* @param list
* 列表數據
* @param fieldName
* 屬性名數組
* @param contentStyle
* 內容樣式
* @param isHaveSerial
* 是否添加序號
* @param startRow
* 起始行
*/
public static <T> void addContent(HSSFSheet sheet, List<T> list, String[] fieldName, HSSFCellStyle contentStyle,
boolean isHaveSerial, Integer startRow) {
try {
if (CollectionUtil.isEmpty(list)) {
return;
}
if (startRow == null) {
startRow = 2;
}
HSSFRow row = null;
HSSFCell cell = null;
List<T> tList = (List<T>) list;
T t = null;
String value = "";
// 填充列表內容
for (int i = 0; i < list.size(); i++) {
row = sheet.createRow(i + startRow);
t = tList.get(i);
for (int j = 0; j < fieldName.length; j++) {
value = StringUtil.objectToString(getFieldValueByName(t, fieldName[j]));
if (isHaveSerial) {
// 首列加序號
if (row.getCell(0) != null && row.getCell(0).getStringCellValue() != null) {
cell = row.createCell(0);
cell.setCellValue("" + i);
}
cell = row.createCell(j + 1);
} else {
cell = row.createCell(j);
}
cell.setCellValue(value);
cell.setCellStyle(contentStyle);
}
}
for (int j = 1; j < fieldName.length; j++) {
sheet.autoSizeColumn(j); // 單元格寬度 以最大的爲準
}
} catch (Throwable e) {
logger.error("填充內容出現錯誤:" + e.getMessage(), e);
}
}
/**
* 獲取某個對象指定屬性的值
*
* @param obj
* @param fieldName
* @return
*/
@SuppressWarnings("rawtypes")
public static Object getFieldValueByName(Object obj, String fieldName) {
try {
if (StringUtil.isEmpty(fieldName) || obj == null) {
return null;
}
Object fieldValue = null;
// 如果是map直接根據key取值
if (obj instanceof Map) {
Map map = (Map) obj;
fieldValue = map.get(fieldName);
} else {
// 如果是javabean對象,直接反射調用getter方法
fieldValue = ReflectUtil.invokeGetterMethod(obj, fieldName);
}
return fieldValue;
} catch (Throwable e) {
logger.error("獲取屬性值出現異常:" + e.getMessage(), e);
return null;
}
}
}
下面進行簡要解析
buildExcelDocument是構造表格的核心回調方法,首先獲取外部傳過來的一些數據和屬性,然後在用這些數據設置表頭和列表內容,然後進行輸出
getHeaderStyle是自定義的表格第一行名稱的樣式
getContentStyle是自定義表格內容列表包括表頭的樣式
addTitle是添加標題和表頭,作爲表格的第一第二行
addContent是添加表格的實際列表內容
上面是涉及到表格導出的一些核心方法,另外還涉及到一些輔助方法,簡單介紹下:
getFieldValueByName作用是對數據庫查出來的數據對象,根據其屬性獲取對應的值,比如根據name屬性獲取值'張三'
ReflectUtil的invokeGetterMethod是一個工具方法,反射調用某個屬性的getter方法,相關方法如下:
/**
* 反射執行某個屬性的getter方法
*
* @param obj
* @param fieldName
* @return
*/
public static Object invokeGetterMethod(Object obj, String fieldName) {
return invokeMethod(obj, getGetterName(fieldName), null);
}
/**
* 根據getter方法的規則,構造某個屬性的get方法名
*
* @param fieldName
* 屬性名
* @return
*/
public static String getGetterName(String fieldName) {
// 屬性的第一個字母轉換爲大寫
return "get" + fieldName.substring(0, 1) + fieldName.substring(1);
}
/**
* 通過類對象,運行指定方法
*
* @param obj
* 類對象
* @param methodName
* 方法名
* @param params
* 參數值
* @return 失敗返回null
*/
public static Object invokeMethod(Object obj, String methodName, Object[] params) {
if (obj == null || StringUtil.isEmpty(methodName)) {
logger.error("參數異常,obj=" + obj + ",methodName=" + methodName);
return null;
}
Class<?> clazz = obj.getClass();
try {
Class<?>[] paramTypes = null;
if (params != null) {
paramTypes = new Class[params.length];
for (int i = 0; i < params.length; ++i) {
paramTypes[i] = params[i].getClass();
}
}
Method method = clazz.getMethod(methodName, paramTypes);
method.setAccessible(true);
return method.invoke(obj, params);
} catch (NoSuchMethodException e) {
logger.error("method " + methodName + " not found in " + obj.getClass().getName());
} catch (Exception e) {
logger.error("Exception:" + e);
}
return null;
}
StringUtil的objectToString用來將其他數據類型統一成字符串,便於顯示:
/**
* 將對象轉換爲字符串
*
* @param object
* @return
*/
public static String objectToString(Object object) {
if (object == null) {
return "";
}
if (object instanceof Date) {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return df.format((Date) object);
}
if (object instanceof String) {
return (String) object;
}
if (object instanceof Integer) {
return ((Integer) object).intValue() + "";
}
if (object instanceof Double) {
return ((Double) object).doubleValue() + "";
}
if (object instanceof Long) {
return Long.toString(((Long) object).longValue());
}
if (object instanceof Float) {
return Float.toHexString(((Float) object).floatValue());
}
if (object instanceof Boolean) {
return Boolean.toString((Boolean) object);
}
if (object instanceof Short) {
return Short.toString((Short) object);
}
return object.toString();
}
到此爲止,介紹完畢,這些方法都是非常實用的乾貨,每個方法都有詳細的註釋,一目瞭然。