Java實現(xiàn)Excel導入導出的步驟詳解
一、導入
前言:導入必須用post請求
具體原因在2中敘述
1、Excel導入
總結(jié)一下目標,就是要將excel中的數(shù)據(jù)行、逐一提取,最后得到一個list,這個list的每個元素就是excel的每個數(shù)據(jù)行的實例,之后的操作就是常規(guī)的java邏輯處理了。
可以把這個過程分為三步:
1)接收數(shù)據(jù)
后臺使用 MultipartFile 接收文件數(shù)據(jù)(這里復習一下,springmvc接收參數(shù)的幾種方式)。
前端如果是前后端分離項目我們不用管,
如果是jsp項目可以用文件標簽<input type="file" name="" value=""/>傳參。
2)提取數(shù)據(jù)
提取數(shù)據(jù)的底層是用IO流實現(xiàn)的,我們這里使用封裝好的工具類。excel工具類有很多、很多,我一般是使用適配性最好的,不然一會兒springboot項目、一會兒spring項目等等,還得換不同的工具類;效率什么的不是首要考慮項。
使用工具類后,我們就得到了一個List<List<String>>:Excel的每個數(shù)據(jù)行組成一個List<String>,多個數(shù)據(jù)行就組成了List<List<String>>
這里有一個小坑,說明一下;有時候你導入的數(shù)據(jù),如果是數(shù)字比如32位的卡號等等,用工具類提取出來成了科學計數(shù)法、或者后面加了小數(shù)點;這就說明你用的這個工具類沒有將數(shù)字類型數(shù)據(jù)進行處理,你需要在工具類中找到數(shù)字類型,添加toText()方法。
當然你也可以使用我后面提供的工具類,這個問題已經(jīng)作了處理。
3)將list的元素處理成實例對象,方便后續(xù)處理
在作轉(zhuǎn)換的時候,還可以加一些校驗、限制,比如限制excel導入總行數(shù)不得超過多少、限制某列參數(shù)不能重復等等。
下面提供一下我用的excel導入工具類,springboot或者spring項目都可以用
public class ImportExeclUtil {
private static int totalRows = 0;// 總行數(shù)
private static int totalCells = 0;// 總列數(shù)
private static String errorInfo;// 錯誤信息
/** 無參構(gòu)造方法 */
public ImportExeclUtil()
{
}
public static int getTotalRows()
{
return totalRows;
}
public static int getTotalCells()
{
return totalCells;
}
public static String getErrorInfo()
{
return errorInfo;
}
/**
*
* 根據(jù)流讀取Excel文件
*
*
* @param inputStream
* @param isExcel2003
* @return
* @see [類、類#方法、類#成員]
*/
public List<List<String>> read(InputStream inputStream, boolean isExcel2003)
throws IOException
{
List<List<String>> dataLst = null;
/** 根據(jù)版本選擇創(chuàng)建Workbook的方式 */
Workbook wb = null;
if (isExcel2003)
{
wb = new HSSFWorkbook(inputStream);
}
else
{
wb = new XSSFWorkbook(inputStream);
}
dataLst = readDate(wb);
return dataLst;
}
/**
*
* 讀取數(shù)據(jù)
*
* @param wb
* @return
* @see [類、類#方法、類#成員]
*/
private List<List<String>> readDate(Workbook wb)
{
List<List<String>> dataLst = new ArrayList<List<String>>();
/** 得到第一個shell */
Sheet sheet = wb.getSheetAt(0);
/** 得到Excel的行數(shù) */
totalRows = sheet.getPhysicalNumberOfRows();
/** 得到Excel的列數(shù) */
if (totalRows >= 1 && sheet.getRow(0) != null)
{
totalCells = sheet.getRow(0).getPhysicalNumberOfCells();
}
/** 循環(huán)Excel的行 */
for (int r = 1; r < totalRows; r++)
{
Row row = sheet.getRow(r);
if (row == null)
{
continue;
}
List<String> rowLst = new ArrayList<String>();
/** 循環(huán)Excel的列 */
for (int c = 0; c < getTotalCells(); c++)
{
Cell cell = row.getCell(c);
String cellValue = "";
if (null != cell)
{
// 以下是判斷數(shù)據(jù)的類型
switch (cell.getCellTypeEnum())
{
case NUMERIC: // 數(shù)字
//如果是日期的話
if(cell != null && HSSFDateUtil.isCellDateFormatted(cell)){
Date d = cell.getDateCellValue();
DateFormat formater = new SimpleDateFormat("yyyy/MM/dd");
String da = formater.format(d);
cellValue = da;
break;
}
cellValue = NumberToTextConverter.toText(cell.getNumericCellValue());
break;
case STRING: // 字符串
cellValue = cell.getStringCellValue();
break;
case BOOLEAN: // Boolean
cellValue = cell.getBooleanCellValue() + "";
break;
case FORMULA: // 公式
cellValue = cell.getCellFormula() + "";
break;
case BLANK: // 空值
cellValue = "";
break;
case ERROR: // 故障
cellValue = "非法字符";
break;
default:
cellValue = "未知類型";
break;
}
}
rowLst.add(cellValue);
}
/** 保存第r行的第c列 */
dataLst.add(rowLst);
}
return dataLst;
}
/**
*
* 根據(jù)Excel表格中的數(shù)據(jù)判斷類型得到值
*
* @param cell
* @return
* @see [類、類#方法、類#成員]
*/
/*private static String getCellValue(Cell cell)
{
String cellValue = "";
if (null != cell)
{
// 以下是判斷數(shù)據(jù)的類型
switch (cell.getCellType())
{
case HSSFCell.CELL_TYPE_NUMERIC: // 數(shù)字
;: // 數(shù)字
if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell))
{
Date theDate = cell.getDateCellValue();
SimpleDateFormat dff = new SimpleDateFormat("yyyy-MM-dd");
cellValue = dff.format(theDate);
}
else
{
DecimalFormat df = new DecimalFormat("0");
cellValue = df.format(cell.getNumericCellValue());
}
break;
case HSSFCell.CELL_TYPE_STRING: // 字符串
cellValue = cell.getStringCellValue();
break;
case HSSFCell.CELL_TYPE_BOOLEAN: // Boolean
cellValue = cell.getBooleanCellValue() + "";
break;
case HSSFCell.CELL_TYPE_FORMULA: // 公式
cellValue = cell.getCellFormula() + "";
break;
case HSSFCell.CELL_TYPE_BLANK: // 空值
cellValue = "";
break;
case HSSFCell.CELL_TYPE_ERROR: // 故障
cellValue = "非法字符";
break;
default:
cellValue = "未知類型";
break;
}
}
return cellValue;
}*/
/**
*
* 根據(jù)實體成員變量的類型得到成員變量的值
*
* @param realValue
* @param fields
* @param f
* @param cellValue
* @return
* @see [類、類#方法、類#成員]
*/
private static Object getEntityMemberValue(Object realValue, Field[] fields, int f, String cellValue)
{
String type = fields[f].getType().getName();
switch (type)
{
case "char":
case "java.lang.Character":
case "java.lang.String":
realValue = cellValue;
break;
case "java.util.Date":
realValue = StringUtils.isBlank(cellValue) ? null : DateUtil.strToDate(cellValue, DateUtil.YYYY_MM_DD);
break;
case "java.lang.Integer":
realValue = StringUtils.isBlank(cellValue) ? null : Integer.valueOf(cellValue);
break;
case "int":
case "float":
case "double":
case "java.lang.Double":
case "java.lang.Float":
case "java.lang.Long":
case "java.lang.Short":
case "java.math.BigDecimal":
realValue = StringUtils.isBlank(cellValue) ? null : new BigDecimal(cellValue);
break;
default:
break;
}
return realValue;
}
/**
*
* 根據(jù)路徑或文件名選擇Excel版本
*
*
* @param filePathOrName
* @param in
* @return
* @throws IOException
* @see [類、類#方法、類#成員]
*/
public static Workbook chooseWorkbook(String filePathOrName, InputStream in)
throws IOException
{
/** 根據(jù)版本選擇創(chuàng)建Workbook的方式 */
Workbook wb = null;
boolean isExcel2003 = ExcelVersionUtil.isExcel2003(filePathOrName);
if (isExcel2003)
{
wb = new HSSFWorkbook(in);
}
else
{
wb = new XSSFWorkbook(in);
}
return wb;
}
static class ExcelVersionUtil
{
/**
*
* 是否是2003的excel,返回true是2003
*
*
* @param filePath
* @return
* @see [類、類#方法、類#成員]
*/
public static boolean isExcel2003(String filePath)
{
return filePath.matches("^.+\\.(?i)(xls)$");
}
/**
*
* 是否是2007的excel,返回true是2007
*
*
* @param filePath
* @return
* @see [類、類#方法、類#成員]
*/
public static boolean isExcel2007(String filePath)
{
return filePath.matches("^.+\\.(?i)(xlsx)$");
}
}
public static class DateUtil
{
// ======================日期格式化常量=====================//
public static final String YYYY_MM_DDHHMMSS = "yyyy-MM-dd HH:mm:ss";
public static final String YYYY_MM_DD = "yyyy-MM-dd";
public static final String YYYY_MM = "yyyy-MM";
public static final String YYYY = "yyyy";
public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
public static final String YYYYMMDD = "yyyyMMdd";
public static final String YYYYMM = "yyyyMM";
public static final String YYYYMMDDHHMMSS_1 = "yyyy/MM/dd HH:mm:ss";
public static final String YYYY_MM_DD_1 = "yyyy/MM/dd";
public static final String YYYY_MM_1 = "yyyy/MM";
/**
*
* 自定義取值,Date類型轉(zhuǎn)為String類型
*
* @param date 日期
* @param pattern 格式化常量
* @return
* @see [類、類#方法、類#成員]
*/
public static String dateToStr(Date date, String pattern)
{
SimpleDateFormat format = null;
if (null == date)
return null;
format = new SimpleDateFormat(pattern, Locale.getDefault());
return format.format(date);
}
/**
* 將字符串轉(zhuǎn)換成Date類型的時間
* <hr>
*
* @param s 日期類型的字符串<br>
* datePattern :YYYY_MM_DD<br>
* @return java.util.Date
*/
public static Date strToDate(String s, String pattern)
{
if (s == null)
{
return null;
}
Date date = null;
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
try
{
date = sdf.parse(s);
}
catch (ParseException e)
{
e.printStackTrace();
}
return date;
}
}
}再提供一個應(yīng)用實例:
@ApiOperation(value = "以導入excel方式,上傳要申請學分的用戶")
@GetMapping(value = "/uuApplyUserInfo")
public AjaxResult uuApplyUserInfo(@RequestParam(value = "files",required = false) MultipartFile files) {
try {
//工具類
ImportExeclUtil readExcelUtil = new ImportExeclUtil();
List<List<String>> read = readExcelUtil.read(files.getInputStream(), true);
if (CollectionUtils.isNotEmpty(read)){
List<ApplyCreditUserDto> importList = read.stream().map(e -> {
ApplyCreditUserDto importDto = new ApplyCreditUserDto();
importDto.setUserName(e.get(0));
importDto.setCreditCardNo(e.get(1));
importDto.setCreditCardPwd(e.get(2));
return importDto;
}).collect(Collectors.toList());
if (CollectionUtils.isEmpty(importList)){
return AjaxResult.error("不能導入空文件");
}
//最多導入1W條
final int maxInt = 10000;
if (importList.size() > maxInt){
return AjaxResult.error("導入最多修改1W條");
}
List<String> orderIds = importList.stream()
.map(ApplyCreditUserDto::getUserName)
.distinct()
.collect(Collectors.toList());
if (!Objects.equals(orderIds.size(),importList.size())){
return AjaxResult.error("導入信息中,有用戶");
}
//調(diào)用業(yè)務(wù)層
return applyCreditLogService.uuApplyUserInfo(importList);
}else{
return AjaxResult.error("不能導入空文件");
}
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("導入失敗,更新數(shù)據(jù)庫時報錯!報錯信息:" + e.toString());
}
}2、開發(fā)中遇到的問題
1)報錯"Unable to process parts as no multi-part configuration has been provided "
報錯信息截取
“MultipartException: Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: Unable to process parts as no multi-part configuration has been provided”
a、如果你在網(wǎng)上搜這個問題,搜到的回答大多是讓你在servlet配置中加配置
<multipart-config> <max-file-size>20848820</max-file-size> <max-request-size>418018841</max-request-size> <file-size-threshold>1048576</file-size-threshold> </multipart-config>
b、但是采用這個答案前,你要看你的項目是否適用上述情況。
SpringMVC處理multipart請求(解析文件請求),有兩種實現(xiàn):CommonsMultipartResolver和StandardServletMultipartResolver。
他們的區(qū)別我這里不作詳細敘述,就講一點:
在servlet中配置,是StandardServletMultipartResolver的配置方式;
而CommonsMultipartResolver的配置方式,是在applicationContext中配置
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8" />
<property name="maxUploadSize" value="31457280" />
<property name="maxInMemorySize" value="4194304" />
</bean>兩種配置的目標基本一致,但是配置位置、對象不一樣,搞混了不僅解決不了問題,還會導致其他問題。
c、我這樣說,是因為上傳文件是基礎(chǔ)配置,對于已經(jīng)運行很久的項目,上傳配置前輩們應(yīng)該早已配置完好,后來人遇到的問題,大多不是缺少“基礎(chǔ)”配置引起的,望大家謹慎修改。
d、回到的我的問題,項目采用的是CommonsMultipartResolver解析文件流,我給大家看一下我發(fā)現(xiàn)問題的過程:
請求來了先去MultipartFilter過濾器,檢測請求是否為包含文件流的請求,打斷點可以看到

我不知道人家業(yè)務(wù)邏輯是咋處理的,只能step over一步一步往下運行,走到下圖這里我大概明白了一點

這里是判斷這個請求是否為“multipart”類型請求,也就是判斷是否需要專門解析文件流的部件出手,結(jié)果我這兒走到了else里邊!
說明檢測結(jié)論是,我的請求不是multipart請求,或者不符合人家的規(guī)范;
于是我重新請求,進入它的判斷方法multipartResolver.isMultipart(processedRequest);
里邊是
@Override
public boolean isMultipart(HttpServletRequest request) {
return (request != null && ServletFileUpload.isMultipartContent(request));
}又一層判斷,接著往里邊方法看ServletFileUpload.isMultipartContent(request))

看到這里,我才看明白,人家要求請求必須是Post請求,我尼瑪!浪費足足一天時間啊。
之所以沒記住是因為“知其然而不知其所以然”,只有知道它的正在原因,才好記住長時間不忘。
其實原因很簡單,get請求有大小限制,所以上傳功能需要用post請求。
get請求有大小限制這種說法其實也不準確,其實http協(xié)議對get請求沒有大小、長度限制,限制產(chǎn)生在瀏覽器和服務(wù)器。
服務(wù)器一般限制get請求大小在8kb以內(nèi);
而瀏覽器又隨型號不通、限制也各不相同,MSIE和Safari的長度限制是2kb,Opear是4kb,F(xiàn)irefox是8KB…
這也解釋了,為什么我搜“get請求大小限制是多少”這個問題,回答都各不一樣,大家都是“知其然而不知其所以然”、得過且過、斷章取義、管中窺豹,我們這種得過且過的態(tài)度,就會使本來簡單的問題變得越來越復雜,因為它本身已經(jīng)疊加了太多其他地方的問題。
根據(jù)瀏覽器限制不同,為了保險你可以取限制最小值2kb,然后說“get請求大小限制為2kb”;
又因為一般瀏覽器限制在4kb,所以又可以說“get請求大小限制為4kb”;
又又因為實際http協(xié)議對get請求沒有限制,所以有人說“get請求沒有大小限制”。。。
嗟乎!希望我們程序員,對自己寫的技術(shù)類文檔,都適當嚴謹一些,這樣才能營造出更好的社區(qū)環(huán)境。
二、導出
前言
導出excel可以分為兩類:
一類是導出excel,里邊有我們查詢的數(shù)據(jù),多用于導出數(shù)據(jù);
另一類是導出excel文件,多用于導出模板、而且對模板格式有要求,這時我們提前建一個模板文件,然后存到服務(wù)器,導出的時候直接把這個文件傳給用戶,這種方法其實已經(jīng)無所謂是什么格式的文件了。
為了方便描述,我們把第一類叫導出excel,第二類叫下載excel模板
1、導出excel
實際運用示例:
@PostMapping("/export")
@ResponseBody
public void export(HttpServletResponse response, @RequestBody JcWecomTag param){
// 設(shè)置response的上下文類型
response.setContentType("octets/stream");
String excelName = "標簽數(shù)據(jù)";
try {
// 創(chuàng)建一個文件對象
HSSFWorkbook workbook = new HSSFWorkbook();
// 用文件對象創(chuàng)建一個sheet頁,并給sheet頁命名
HSSFSheet sheet0 = workbook.createSheet("標簽");
// 用sheet對象創(chuàng)建一個行對象
HSSFRow rowm0 = sheet0.createRow(0);
// 用行對象創(chuàng)建單元格,給單元格賦值
HSSFCell cellColName0 = rowm0.createCell(0);
cellColName0.setCellType(CellType.STRING);
HSSFRichTextString text0 = new HSSFRichTextString("標簽ID");
cellColName0.setCellValue(text0);
HSSFCell cellColName1 = rowm0.createCell(1);
cellColName1.setCellType(CellType.STRING);
HSSFRichTextString text1 = new HSSFRichTextString("標簽名稱");
cellColName1.setCellValue(text1);
HSSFCell cellColName2 = rowm0.createCell(2);
cellColName2.setCellType(CellType.STRING);
HSSFRichTextString text2 = new HSSFRichTextString("標簽分組ID");
cellColName2.setCellValue(text2);
HSSFCell cellColName3 = rowm0.createCell(3);
cellColName3.setCellType(CellType.STRING);
HSSFRichTextString text3 = new HSSFRichTextString("標簽分組名稱");
cellColName3.setCellValue(text3);
HSSFCell cellColName4 = rowm0.createCell(4);
cellColName4.setCellType(CellType.STRING);
HSSFRichTextString text4 = new HSSFRichTextString("bdp字典編碼");
cellColName4.setCellValue(text4);
HSSFCell cellColName5 = rowm0.createCell(5);
cellColName5.setCellType(CellType.STRING);
HSSFRichTextString text5 = new HSSFRichTextString("bdp字典名稱");
cellColName5.setCellValue(text5);
// 查數(shù)據(jù)
List<JcWecomTag> tagList = wecomTagService.mulitQueryTag(param);
// 將查詢到的數(shù)據(jù)設(shè)置到sheet對應(yīng)的單元格中
for (int i = 0; i < tagList.size(); i++) {
JcWecomTag tag = tagList.get(i);
HSSFRow row = sheet0.createRow(i + 1);// 創(chuàng)建所需的行數(shù)
HSSFCell cell = null;
cell = row.createCell(0, CellType.STRING);
cell.setCellValue(tag.getTagId());
cell = row.createCell(1, CellType.STRING);
cell.setCellValue(tag.getTagName());
cell = row.createCell(2, CellType.STRING);
cell.setCellValue(tag.getTagGroupId());
cell = row.createCell(3, CellType.STRING);
cell.setCellValue(tag.getTagGroupName());
cell = row.createCell(4, CellType.STRING);
cell.setCellValue(tag.getDicCode());
}
// 轉(zhuǎn)碼防止亂碼
response.addHeader("Content-Disposition", "attachment;filename="
+ new String(excelName.getBytes("UTF-8"), "ISO8859-1")
+ ".xls");
OutputStream out = response.getOutputStream();
ExcelUtil.writeBySheetAndRow(workbook, out);
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}1)示例是SpringMVC項目,Controller接口必須加@ResponseBody注解,否則返回值會被SpringMVC視為視圖、會報404錯誤;
2)最后用到了一個excel工具ExcelUtil.writeBySheetAndRow(workbook, out);
這個工具去網(wǎng)上搜一下就行(估計你們項目本身就有)。導出功能的核心是:配置好Excel工作簿對象HSSFWorkbook ,對象包含標題行、數(shù)據(jù)行、單元格數(shù)據(jù);以及配置好輸出流OutputStream;這兩個的配置,如上面示例所示配置即可。
這兩個對象都弄好之后,剩下的、一般Excel工具類都能處理,不用擔心。
2、使用自定義注解-導出excel
1)引入依賴
這里用的是poi導出excel,版本如下
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.1</version>
</dependency>2)自定義注解
為了方便,提高復用效率,這里自定義2個注解;
第一個:@EnableExcel,用來開啟Excel表格的導出,用在裝導出數(shù)據(jù)的實體類上;
/**
* 標記類是否開啟Excel
* @Author: Sunlong
* @date: 2020/5/10 20:29
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableExcel {
}第二個:@ExcelRow,用在裝導出數(shù)據(jù)的實體類的屬性上,用來映射字段與excel的對應(yīng)關(guān)系;
/**
* excel 表格 列名注解
*
* @author sunlong
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelRow {
/**
* Excel 對應(yīng)列名
* @return
*/
String name();
/**
* excel 列名備注
* @return
*/
String note() default "";
}3)代碼邏輯—提取導出工具類
a、通過反射獲取自定義注解EnableExcel 判斷是否開啟Excel導出
b、通過反射獲取自定義注解ExcelRow 獲取列對應(yīng)的屬性
c、把屬性對應(yīng)的列下標取出來,屬性名做為key,下標做為value放到map中
d、遍歷要導出的數(shù)據(jù)集合,通過屬性描述器PropertyDescriptor獲取對應(yīng)屬性下標及屬性值并設(shè)置到cell單元格中
還是為了方便,已經(jīng)提高復用效率,我們將上述代碼提取成一個工具類,如下:
public class ExportExcelUtils {
/**
* workbook
* @param titleList
* @return
*/
public static HSSFWorkbook getWorkBook(List<String> titleList){
//第一步,創(chuàng)建一個workbook,對應(yīng)一個Excel文件
HSSFWorkbook wb = new HSSFWorkbook();
// 一個sheet
HSSFSheet sheet = wb.createSheet("sheet1");
HSSFRow rowTitle = sheet.createRow(0); // 第一行 標題
// 設(shè)置標題
for (int i = 0; i < titleList.size(); i++) {
HSSFCell cell = rowTitle.createCell(i);
cell.setCellValue(titleList.get(i));
}
//合并單元格CellRangeAddress構(gòu)造參數(shù)依次表示起始行,截至行,起始列, 截至列
/*sheet.addMergedRegion(new CellRangeAddress(0,0,0,4));
sheet.addMergedRegion(new CellRangeAddress(titleList.size()-1,titleList.size()-1,titleList.size()-1,titleList.size()+1));*/
return wb;
}
public static <T> HSSFWorkbook getWorkBook(List<String> titleList , List<T> dataList) throws IntrospectionException, InvocationTargetException, IllegalAccessException {
if (CollectionUtils.isNotEmpty(dataList)) {
T t1 = dataList.get(0);
Class<?> t1Class = t1.getClass();
EnableExcel enableExcel = t1Class.getAnnotation(EnableExcel.class);
if (enableExcel == null) {
throw new IllegalArgumentException("EnableExcel 注解沒有在實體類啟用");
}
Field[] fields = t1Class.getDeclaredFields();
if (fields != null && fields.length > 0) {
Map<String , Integer> titleMap = new HashMap<>(titleList.size()); // 存放屬性名稱對應(yīng)的下標
int fieldExcelSize = 0; // 類中ExcelRow 注解的數(shù)量
for (Field field : fields) {
field.setAccessible(true);
String fieldName = field.getName();
ExcelRow excelRow = field.getAnnotation(ExcelRow.class);
if (excelRow != null) {
String name = excelRow.name();
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("ExcelRow 注解name屬性不能為空");
}
int index = titleList.indexOf(name.trim());
if (index != -1) {
fieldExcelSize++;
titleMap.put(fieldName , index);
}
}
}
if (!(titleList.size() == fieldExcelSize)) {
throw new IllegalArgumentException("ExcelRow 注解name屬性對應(yīng)的列數(shù)不對");
}
HSSFWorkbook workBook = getWorkBook(titleList);
HSSFSheet sheet = workBook.getSheetAt(0);
for (T t : dataList) {
int lastRowNum = sheet.getLastRowNum();
HSSFRow row = sheet.createRow(lastRowNum + 1);
BeanInfo beanInfo = Introspector.getBeanInfo(t.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
String fieldName = propertyDescriptor.getName();
if (titleMap.containsKey(fieldName)) {
Method readMethod = propertyDescriptor.getReadMethod();
if (readMethod != null) {
Class<?> returnType = readMethod.getReturnType();
String simpleName = returnType.getSimpleName();
Object invoke = readMethod.invoke(t);
String value = "";
// 可以根據(jù)不同的類型返回不同的數(shù)據(jù)
if ("date".equalsIgnoreCase(simpleName)) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
if (invoke != null) {
value = simpleDateFormat.format(invoke);
}
}
if (invoke != null && "".equals(value)) {
value = invoke.toString();
}
row.createCell(titleMap.get(fieldName)).setCellValue(value);
}
}
}
}
return workBook;
}
}
return null;
}
}4)應(yīng)用實例
創(chuàng)建一個用來裝導出數(shù)據(jù)的類(為了不與項目其他功能沖突,我一般都是新建一個專門做導出的實體類)
@EnableExcel
@Data
public class UserEntity {
@ExcelRow(name = "name")
private String username;
@ExcelRow(name = "pass")
private String password;
@ExcelRow(name = "date")
private Date createDate;
}模擬導出功能:
public class Test {
public static void main(String[] args) throws IllegalAccessException, IntrospectionException, InvocationTargetException, IOException {
List<String> titleList = new ArrayList<>();
titleList.add("name");
titleList.add("pass");
titleList.add("date");
List<UserEntity> userEntities = new ArrayList<>();
for (int i = 0; i < 10; i++) {
UserEntity userEntity1 = new UserEntity();
userEntity1.setUsername("username"+i);
userEntity1.setPassword("password"+i);
userEntity1.setCreateDate(new Date());
userEntities.add(userEntity1);
}
HSSFWorkbook workBook = ExportExcelUtils.getWorkBook(titleList, userEntities);
if (workBook != null) {
File file = new File("D:\\test_export.xlsx");
workBook.write(file);
}
}
}3、使用easyPOI–導出excel
1)easyPOI簡介
easyPOI是一個以Apache poi為基礎(chǔ)的工具包、一款開源框架,用于實現(xiàn)excel,word,pdf的導入導出;開發(fā)者為Lemur。
特性
基于注解的導入導出,修改注解就可以修改Excel
支持常用的樣式自定義
基于map可以靈活定義的表頭字段
支持一對多的導入、導出
支持模板的導出,一些常見的標簽,自定義標簽
支持HTML/Excel轉(zhuǎn)換
支持word的導出,支持圖片,Excel
2)導出Excel使用實例
a)添加依賴
<dependency>
<groupId>cn.easyproject</groupId>
<artifactId>orai18n</artifactId>
</dependency>需要注意的是由于easypoi的依賴內(nèi)部依賴原生的poi,所以,引入了easypoi的依賴之后,需要把原生的poi的依賴刪掉
b)需要引入一個easyPoi導入導出工具類
/**
* 描述:easyPoi導入導出類
* @author 梅西庫里RNG
*/
@Slf4j
public class EasyPoiExcelUtil {
public static String dictColIndex="colIndex";
public static String dictColValue="colDicts";
/**
* 常用普通導出
* @param list
* @param sheetName
* @param pojoClass
* @return
*/
public static Workbook exportExcel(List<?> list, String sheetName,Class<?> pojoClass) {
return exportExcel(list,sheetName,pojoClass,null);
}
/**
* 常用普通導出 帶字典
* @param list
* @param sheetName
* @param pojoClass
* @param dicts
* @return
*/
public static Workbook exportExcel(List<?> list, String sheetName,Class<?> pojoClass,List<Map<String,Object>> dicts) {
return exportExcel(list,null,sheetName,pojoClass,true,dicts);
}
/**
* 常用普通導入
* @auther zhangdongsheng
* @param file
* @param pojoClass
* @param <T>
* @return
*/
public static <T> List<T> importExcel(MultipartFile file,Class<T> pojoClass) {
return importExcel(file,0,1,pojoClass);
}
/**
* 得到Workbook對象
*
* @param file
* @return
* @throws IOException
*/
private static Workbook getWorkBook(MultipartFile file) throws IOException {
//這樣寫 excel 能兼容03和07
InputStream is = file.getInputStream();
Workbook hssfWorkbook = null;
try {
hssfWorkbook = new HSSFWorkbook(is);
} catch (Exception ex) {
is = file.getInputStream();
hssfWorkbook = new XSSFWorkbook(is);
}
return hssfWorkbook;
}
/**
* 得到錯誤信息
*
* @param sb
* @param list
* @param i
* @param obj
* @param name 用哪個屬性名去表明不和規(guī)定的數(shù)據(jù)
* @param msg
* @throws Exception
*/
private static void getWrongInfo(StringBuilder sb, List list, int i, Object obj, String name, String msg) {
Class clazz = obj.getClass();
Object str = null;
//得到屬性名數(shù)組
Field[] fields = clazz.getDeclaredFields();
try{
for (Field f : fields) {
if (f.getName().equals(name)) {
//用來得到屬性的get和set方法
PropertyDescriptor pd = new PropertyDescriptor(f.getName(), clazz);
//得到get方法
Method getMethod = pd.getReadMethod();
str = getMethod.invoke(obj);
}
}
if (i == 0) {
sb.append(msg + str + ";");
} else if (i == (list.size() - 1)) {
sb.append(str + "</br>");
} else {
sb.append(str + ";");
}
}catch (Exception e){
log.error("程序異常:{}",e);
}
}
public static Workbook exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, boolean isCreateHeader,List<Map<String,Object>> dicts) {
ExportParams exportParams = new ExportParams();
exportParams.setSheetName(sheetName);
if (title!=null){
exportParams.setTitle(sheetName);
}
exportParams.setCreateHeadRows(isCreateHeader);
return defaultExport(list, pojoClass, exportParams,dicts);
}
public static Workbook exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass) {
return defaultExport(list, pojoClass, new ExportParams(title, sheetName,ExcelType.XSSF));
}
public static Workbook exportExcel(List<Map<String, Object>> list) {
return defaultExport(list);
}
private static Workbook defaultExport(List<?> list, Class<?> pojoClass, ExportParams exportParams) {
return ExcelExportUtil.exportExcel(exportParams, pojoClass, list);
}
private static Workbook defaultExport(List<?> list, Class<?> pojoClass, ExportParams exportParams,List<Map<String,Object>> dicts) {
int rowCnt=list!=null&&list.size()>0?list.size():0;
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, pojoClass, list);
if(dicts!=null&&dicts.size()>0){
if(workbook!=null&&workbook.getNumberOfSheets()>0){
Sheet sheet =workbook.getSheetAt(0);
if(rowCnt>0){
int curRow=1;//一般會帶頭部 從第1行開始
for (int i = 0; i < rowCnt; i++) {
int firstRow=i+curRow;
for(Map<String,Object> map:dicts) {
int firstCol=(int)map.get(dictColIndex);
String[] dict=(String[])map.get(dictColValue);
CellRangeAddressList cellRangeAddressList = new CellRangeAddressList(firstRow, firstRow, firstCol, firstCol);
DVConstraint dvConstraint = DVConstraint.createExplicitListConstraint(dict);
HSSFDataValidation dataValidation = new HSSFDataValidation(cellRangeAddressList, dvConstraint);
sheet.addValidationData(dataValidation);
}
}
}
}
}
return workbook;
}
private static Workbook defaultExport(List<Map<String, Object>> list) {
return ExcelExportUtil.exportExcel(list, ExcelType.XSSF);
}
private static <T> List<T> importExcel(String filePath, Integer titleRows, Integer headerRows, Class<T> pojoClass) {
if (StringUtils.isBlank(filePath)) {
return new ArrayList<>();
}
ImportParams params = new ImportParams();
params.setTitleRows(titleRows);
params.setHeadRows(headerRows);
List<T> list = null;
try {
list = ExcelImportUtil.importExcel(new File(filePath), pojoClass, params);
}catch (Exception e) {
log.error("程序異常",e);
}
return list;
}
private static <T> List<T> importExcel(MultipartFile file, Integer titleRows, Integer headerRows, Class<T> pojoClass) {
if (file == null) {
return new ArrayList<>();
}
ImportParams params = new ImportParams();
params.setTitleRows(titleRows);
params.setHeadRows(headerRows);
List<T> list = null;
try {
list = ExcelImportUtil.importExcel(file.getInputStream(), pojoClass, params);
} catch (Exception e) {
log.error("程序異常",e);
}
return list;
}
/**
* 根據(jù)接收的Excel文件來導入Excel,并封裝成實體類
*
* @param file 上傳的文件
* @param titleRows 表標題的行數(shù)
* @param headerRows 表頭行數(shù)
* @param pojoClass Excel實體類
*/
private static <T> ExcelImportResult<T> importExcelVerify(MultipartFile file, Integer titleRows, Integer headerRows, Class<T> pojoClass) {
if (ObjectUtil.isNull(file)) {
return null;
}
ImportParams params = new ImportParams();
params.setTitleRows(titleRows);
params.setHeadRows(headerRows);
//是否開啟校驗
params.setNeedVerify(true);
List<T> list = null;
try {
return ExcelImportUtil.importExcelMore(file.getInputStream(), pojoClass, params);
} catch (Exception e) {
log.error(">>> 導入數(shù)據(jù)異常:{}", e.getMessage());
return null;
}
}
}c)Controller方法
@ApiOperation("列表 導出")
@PostMapping (value="/search/export")
public void exportOrgList(HttpServletResponse response, @RequestBody CustomerVo customerVo) throws Exception {
//查詢
List<CustomerAdminVo> list = customerService.searchAdmin(customerVo);
//調(diào)用工具,生成workbook工作簿對象
Workbook workbook=HysExcelUtil.exportExcel(list,"行政用戶-列表",CustomerAdminVo.class);
//配置response對象
response.setContentType("application/x-excel");
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Disposition","attachment; filename=customer_admin_export.xls");
//生成輸出流對象,并把工作薄寫入到輸出流
OutputStream oStream = response.getOutputStream();// 輸出流
workbook.write(oStream);//把工作薄寫入到輸出流
//關(guān)閉流
try {
oStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}上面代碼中的
HysExcelUtil.exportExcel(list,"行政用戶-列表",CustomerAdminVo.class);
第三個參數(shù)CustomerAdminVo.class,是用來設(shè)定導出excel的列名、列順序、與數(shù)據(jù)的映射關(guān)系、列的寬度、日期格式等;最好給每一個導出模板,配一個獨立的vo類,用以個性化配置。
@ApiModel(value = "CustomerAdminVo", description = "用戶")
@Data
public class CustomerAdminVo implements Serializable {
private static final long serialVersionUID = -3477299713883180124L;
@Excel(name = "賬戶名稱", orderNum = "0", width = 30)
@ApiModelProperty(value = "賬戶名稱 不為空在賬號表有記錄",dataType ="String",required = true)
private String accountName;
@Excel(name = "真實姓名", orderNum = "1", width = 30)
@ApiModelProperty(value = "真實姓名")
private String realName;
@Excel(name = "手機號碼", orderNum = "2", width = 30)
@ApiModelProperty(name = "mobilNumber",value = "手機號碼",dataType ="String")
private String mobilNumber;
@Excel(name = "添加時間", orderNum = "14", expertFormat = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}其中,用于設(shè)置上述內(nèi)容的@Excel注解,就是easyPOI提供的注解
import cn.afterturn.easypoi.excel.annotation.Excel;
注解的屬性中,
name 列名,orderNum 順序,width 列寬,expertFormat 時間字段導出格式(不為空時,按指定格式格式化時間)
d)導出結(jié)果

4、導出excel模板(其實導出word、pdf都可以,就是導出文件的方法)
1)首先,文件要提前放在對應(yīng)的位置,如下圖:

2)導出的controller方法示例:
@RequestMapping("/downloadTemp")
public void downloadFile(HttpServletRequest request, HttpServletResponse response) throws Exception {
String fileName = "org_import.xls"; // 下載的文件名(前提:下載的文件需要存放在服務(wù)器對應(yīng)的位置上)
response.setContentType("text/html;charset=UTF-8");
BufferedInputStream in = null;
BufferedOutputStream out = null;
request.setCharacterEncoding("UTF-8");
String rootpath = request.getSession().getServletContext()
.getRealPath("/");
try {
File f = new File(rootpath + "res/" + fileName);//這里文件名,要和文件放的位置對應(yīng)
response.setContentType("application/x-excel");
response.setCharacterEncoding("UTF-8");
response.setHeader(
"Content-Disposition",
"attachment; filename="
+ new String("機構(gòu)導入.xls".getBytes("gbk"),
"iso-8859-1"));
response.setHeader("Content-Length", String.valueOf(f.length()));
in = new BufferedInputStream(new FileInputStream(f));
out = new BufferedOutputStream(response.getOutputStream());
byte[] data = new byte[1024];
int len = 0;
while (-1 != (len = in.read(data, 0, data.length))) {
out.write(data, 0, len);
}
} catch (Exception e) {
out.write(e.toString().getBytes());
e.printStackTrace();
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}以上就是Java實現(xiàn)Excel導入導出的步驟詳解的詳細內(nèi)容,更多關(guān)于Java實現(xiàn)Excel導入導出的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解決Springboot整合shiro時靜態(tài)資源被攔截的問題
這篇文章主要介紹了解決Springboot整合shiro時靜態(tài)資源被攔截的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01
使用springboot每日推送早安問候語到用戶微信的全過程
近期網(wǎng)上又出現(xiàn)一股給女朋友做微信公眾號推送的潮流,所以這篇文章主要給大家介紹了關(guān)于如何使用springboot每日推送早安問候語到用戶微信的相關(guān)資料,文中通過圖文以及實例代碼介紹的非常詳細,需要的朋友可以參考下2022-11-11
Java基礎(chǔ)教程之八大基本數(shù)據(jù)類型
這篇文章主要給大家介紹了關(guān)于Java基礎(chǔ)教程之八大基本數(shù)據(jù)類型的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧2019-06-06
SpringBoot和VUE源碼直接整合打包成jar的踩坑記錄
這篇文章主要介紹了SpringBoot和VUE源碼直接整合打包成jar的踩坑記錄,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03
Spring Boot 3.x 全新的熱部署配置方式詳解(IntelliJ ID
這篇文章主要介紹了Spring Boot 3.x 全新的熱部署配置方式(IntelliJ IDEA 2023.1),本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-07-07
Java使用枚舉替代if/else和switch-case語句的實踐
在軟件開發(fā)中if-else和switch-case語句經(jīng)常被用來處理不同的條件分支,但在大型項目中,這種做法可能導致代碼可讀性差、維護困難,這篇文章主要給大家介紹了關(guān)于Java使用枚舉替代if/else和switch-case語句的相關(guān)資料,需要的朋友可以參考下2024-09-09

