欧美精品123_精品露脸国产偷人在视频_日韩美女免费线视频_成人av三级

SpringBoot 實現 Excel 導入導出,性能爆表,用起來夠優雅(springboot核心注解)

本文的標題是《SpringBoot 實現 Excel 導入導出,性能爆表,用起來夠優雅》來源于:由作者:陳嫣然采編而成,主要講述了操作Excel實現導入導出是個非常常見的需求,之前介紹了一款非常好用的

**作Excel實現導入導出是個非常常見的需求,之前介紹了一款非常好用的工具EasyPoi。有讀者提出在數據量大的情況下,EasyPoi占用內存大,性能不夠好。今天給大家推薦一款性能更好的Excel導入導出工具Easyexcel,希望對大家有所幫助!

EasyExcel簡介

EasyExcel是一款阿里開源的Excel導入導出工具,具有處理快速、占用內存小、使用方便的特點,在Github上已有22k+Star,可見其非常流行。

EasyExcel讀取75M(46W行25列)的Excel,僅需使用64M內存,耗時20s,極速模式還可以更快!


SpringBoot 實現 Excel 導入導出,性能爆表,用起來夠優雅(springboot核心注解)

集成

在SpringBoot中集成EasyExcel非常簡單,僅需一個依賴即可。

<!--EasyExcel相關依賴-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.0.5</version>
</dependency>

使用

EasyExcel和EasyPoi的使用非常類似,都是通過注解來控制導入導出。接下來我們以會員信息和訂單信息的導入導出為例,分別實現下簡單的單表導出和具有一對多關系的復雜導出。

簡單導出

我們以會員信息的導出為例,來體驗下EasyExcel的導出功能。

  • 首先創建一個會員對象Member,封裝會員信息,這里使用了EasyExcel的注解;

/**
* 購物會員
* Created by macro on 2021/10/12.
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class Member {
@ExcelProperty("ID")
@ColumnWidth(10)
private Long id;
@ExcelProperty("用戶名")
@ColumnWidth(20)
private String username;
@ExcelIgnore
private String password;
@ExcelProperty("昵稱")
@ColumnWidth(20)
private String nickname;
@ExcelProperty("出生日期")
@ColumnWidth(20)
@DateTimeFormat("yyyy-MM-dd")
private Date birthday;
@ExcelProperty("手機號")
@ColumnWidth(20)
private String phone;
@ExcelIgnore
private String icon;
@ExcelProperty(value = "性別", converter = GenderConverter.class)
@ColumnWidth(10)
private Integer gender;
}

  • 上面代碼使用到了EasyExcel的核心注解,我們分別來了解下:
    • @ExcelProperty:核心注解,value屬性可用來設置表頭名稱,converter屬性可以用來設置類型轉換器;
    • @ColumnWidth:用于設置表格列的寬度;
    • @DateTimeFormat:用于設置日期轉換格式。
  • 在EasyExcel中,如果你想實現枚舉類型到字符串的轉換(比如gender屬性中,0->男1->女),需要自定義轉換器,下面為自定義的GenderConverter代碼實現;

/**
* excel性別轉換器
* Created by macro on 2021/12/29.
*/
public class GenderConverter implements Converter<Integer> {
@Override
public Class<?> supportJavaTypeKey() {
//對象屬性類型
return Integer.class;
}

@Override
public CellDataTypeEnum supportExcelTypeKey() {
//CellData屬性類型
return CellDataTypeEnum.STRING;
}

@Override
public Integer convertToJavaData(ReadConverterContext<?> context) throws Exception {
//CellData轉對象屬性
String cellStr = context.getReadCellData().getStringValue();
if (StrUtil.isEmpty(cellStr)) return null;
if ("男".equals(cellStr)) {
return 0;
} else if ("女".equals(cellStr)) {
return 1;
} else {
return null;
}
}

@Override
public WriteCellData<?> convertToExcelData(WriteConverterContext<Integer> context) throws Exception {
//對象屬性轉CellData
Integer cellValue = context.getValue();
if (cellValue == null) {
return new WriteCellData<>("");
}
if (cellValue == 0) {
return new WriteCellData<>("男");
} else if (cellValue == 1) {
return new WriteCellData<>("女");
} else {
return new WriteCellData<>("");
}
}
}

  • 接下來我們在Controller中添加一個接口,用于導出會員列表到Excel,還需給響應頭設置下載excel的屬性,具體代碼如下;

/**
* EasyExcel導入導出測試Controller
* Created by macro on 2021/10/12.
*/
@Controller
@Api(tags = "EasyExcelController", description = "EasyExcel導入導出測試")
@RequestMapping("/easyExcel")
public class EasyExcelController {

@SneakyThrows(IOException.class)
@ApiOperation(value = "導出會員列表Excel")
@RequestMapping(value = "/exportMemberList", method = RequestMethod.GET)
public void exportMemberList(HttpServletResponse response) {
setExcelRespProp(response, "會員列表");
List<Member> memberList = LocalJsonUtil.getListFromJson("json/members.json", Member.class);
EasyExcel.write(response.getOutputStream())
.head(Member.class)
.excelType(ExcelTypeEnum.XLSX)
.sheet("會員列表")
.doWrite(memberList);
}

/**
* 設置excel下載響應頭屬性
*/
private void setExcelRespProp(HttpServletResponse response, String rawFileName) throws UnsupportedEncodingException {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("UTF-8");
String fileName = URLEncoder.encode(rawFileName, "UTF-8").replaceAll("+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
}
}

  • 運行項目,通過Swagger測試接口,注意在Swagger中訪問接口無法直接下載,需要點擊返回結果中的下載按鈕才行,訪問地址:http://localhost:8088/swagger-ui/


SpringBoot 實現 Excel 導入導出,性能爆表,用起來夠優雅(springboot核心注解)

  • 下載完成后,查看下文件,一個標準的Excel文件已經被導出了。


SpringBoot 實現 Excel 導入導出,性能爆表,用起來夠優雅(springboot核心注解)

簡單導入

接下來我們以會員信息的導入為例,來體驗下EasyExcel的導入功能。

  • 在Controller中添加會員信息導入的接口,這里需要注意的是使用@RequestPart注解修飾文件上傳參數,否則在Swagger中就沒法顯示上傳按鈕了;

/**
* EasyExcel導入導出測試Controller
* Created by macro on 2021/10/12.
*/
@Controller
@Api(tags = "EasyExcelController", description = "EasyExcel導入導出測試")
@RequestMapping("/easyExcel")
public class EasyExcelController {

@SneakyThrows
@ApiOperation("從Excel導入會員列表")
@RequestMapping(value = "/importMemberList", method = RequestMethod.POST)
@ResponseBody
public CommonResult importMemberList(@RequestPart("file") MultipartFile file) {
List<Member> memberList = EasyExcel.read(file.getInputStream())
.head(Member.class)
.sheet()
.doReadSync();
return CommonResult.success(memberList);
}
}

  • 然后在Swagger中測試接口,選擇之前導出的Excel文件即可,導入成功后會返回解析到的數據。


SpringBoot 實現 Excel 導入導出,性能爆表,用起來夠優雅(springboot核心注解)

復雜導出

當然EasyExcel也可以實現更加復雜的導出,比如導出一個嵌套了商品信息的訂單列表,下面我們來實現下!

使用EasyPoi實現

之前我們使用過EasyPoi實現該功能,由于EasyPoi本來就支持嵌套對象的導出,直接使用內置的@ExcelCollection注解即可實現,非常方便也符合面向對象的思想。


SpringBoot 實現 Excel 導入導出,性能爆表,用起來夠優雅(springboot核心注解)

尋找方案

由于EasyExcel本身并不支持這種一對多的信息導出,所以我們得自行實現下,這里分享一個我平時常用的快速查找解決方案的辦法。

我們可以直接從開源項目的issues里面去搜索,比如搜索下一對多,會直接找到有無一對多導出比較優雅的方案這個issue。


SpringBoot 實現 Excel 導入導出,性能爆表,用起來夠優雅(springboot核心注解)

從此issue的回復我們可以發現,項目維護者建議創建自定義合并策略來實現,有位回復的老哥已經給出了實現代碼,接下來我們就用這個方案來實現下。


SpringBoot 實現 Excel 導入導出,性能爆表,用起來夠優雅(springboot核心注解)

解決思路

為什么自定義單元格合并策略能實現一對多的列表信息的導出呢?首先我們來看下將嵌套數據平鋪,不進行合并導出的Excel。


SpringBoot 實現 Excel 導入導出,性能爆表,用起來夠優雅(springboot核心注解)

看完之后我們很容易理解解決思路,只要把訂單ID相同的列中需要合并的列給合并了,就可以實現這種一對多嵌套信息的導出了。

實現過程

  • 首先我們得把原來嵌套的訂單商品信息給平鋪了,創建一個專門的導出對象OrderData,包含訂單和商品信息,二級表頭可以通過設置@ExcelProperty的value為數組來實現;

/**
* 訂單導出
* Created by macro on 2021/12/30.
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class OrderData {
@ExcelProperty(value = "訂單ID")
@ColumnWidth(10)
@CustomMerge(needMerge = true, isPk = true)
private String id;
@ExcelProperty(value = "訂單編碼")
@ColumnWidth(20)
@CustomMerge(needMerge = true)
private String orderSn;
@ExcelProperty(value = "創建時間")
@ColumnWidth(20)
@DateTimeFormat("yyyy-MM-dd")
@CustomMerge(needMerge = true)
private Date createTime;
@ExcelProperty(value = "收貨地址")
@CustomMerge(needMerge = true)
@ColumnWidth(20)
private String receiverAddress;
@ExcelProperty(value = {"商品信息", "商品編碼"})
@ColumnWidth(20)
private String productSn;
@ExcelProperty(value = {"商品信息", "商品名稱"})
@ColumnWidth(20)
private String name;
@ExcelProperty(value = {"商品信息", "商品標題"})
@ColumnWidth(30)
private String subTitle;
@ExcelProperty(value = {"商品信息", "品牌名稱"})
@ColumnWidth(20)
private String brandName;
@ExcelProperty(value = {"商品信息", "商品價格"})
@ColumnWidth(20)
private BigDecimal price;
@ExcelProperty(value = {"商品信息", "商品數量"})
@ColumnWidth(20)
private Integer count;
}

  • 然后將原來嵌套的Order對象列表轉換為OrderData對象列表;

/**
* EasyExcel導入導出測試Controller
* Created by macro on 2021/10/12.
*/
@Controller
@Api(tags = "EasyExcelController", description = "EasyExcel導入導出測試")
@RequestMapping("/easyExcel")
public class EasyExcelController {
private List<OrderData> convert(List<Order> orderList) {
List<OrderData> result = new ArrayList<>();
for (Order order : orderList) {
List<Product> productList = order.getProductList();
for (Product product : productList) {
OrderData orderData = new OrderData();
BeanUtil.copyProperties(product,orderData);
BeanUtil.copyProperties(order,orderData);
result.add(orderData);
}
}
return result;
}
}

  • 再創建一個自定義注解CustomMerge,用于標記哪些屬性需要合并,哪個是主鍵;

/**
* 自定義注解,用于判斷是否需要合并以及合并的主鍵
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface CustomMerge {

/**
* 是否需要合并單元格
*/
boolean needMerge() default false;

/**
* 是否是主鍵,即該字段相同的行合并
*/
boolean isPk() default false;
}

  • 再創建自定義單元格合并策略類CustomMergeStrategy,當Excel中兩列主鍵相同時,合并被標記需要合并的列;

/**
* 自定義單元格合并策略
*/
public class CustomMergeStrategy implements RowWriteHandler {
/**
* 主鍵下標
*/
private Integer pkIndex;

/**
* 需要合并的列的下標**
*/
private List<Integer> needMergeColumnIndex = new ArrayList<>();

/**
* DTO數據類型
*/
private Class<?> elementType;

public CustomMergeStrategy(Class<?> elementType) {
this.elementType = elementType;
}

@Override
public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer relativeRowIndex, Boolean isHead) {
// 如果是標題,則直接返回
if (isHead) {
return;
}

// 獲取當前sheet
Sheet sheet = writeSheetHolder.getSheet();

// 獲取標題行
Row titleRow = sheet.getRow(0);

if (null == pkIndex) {
this.lazyInit(writeSheetHolder);
}

// 判斷是否需要和上一行進行合并
// 不能和標題合并,只能數據行之間合并
if (row.getRowNum() <= 1) {
return;
}
// 獲取上一行數據
Row lastRow = sheet.getRow(row.getRowNum() - 1);
// 將本行和上一行是同一類型的數據(通過主鍵字段進行判斷),則需要合并
if (lastRow.getCell(pkIndex).getStringCellValue().equalsIgnoreCase(row.getCell(pkIndex).getStringCellValue())) {
for (Integer needMerIndex : needMergeColumnIndex) {
CellRangeAddress cellRangeAddress = new CellRangeAddress(row.getRowNum() - 1, row.getRowNum(),
needMerIndex, needMerIndex);
sheet.addMergedRegionUnsafe(cellRangeAddress);
}
}
}

/**
* 初始化主鍵下標和需要合并字段的下標
*/
private void lazyInit(WriteSheetHolder writeSheetHolder) {

// 獲取當前sheet
Sheet sheet = writeSheetHolder.getSheet();

// 獲取標題行
Row titleRow = sheet.getRow(0);
// 獲取DTO的類型
Class<?> eleType = this.elementType;

// 獲取DTO所有的屬性
Field[] fields = eleType.getDeclaredFields();

// 遍歷所有的字段,因為是基于DTO的字段來構建excel,所以字段數 >= excel的列數
for (Field theField : fields) {
// 獲取@ExcelProperty注解,用于獲取該字段對應在excel中的列的下標
ExcelProperty easyExcelAnno = theField.getAnnotation(ExcelProperty.class);
// 為空,則表示該字段不需要導入到excel,直接處理下一個字段
if (null == easyExcelAnno) {
continue;
}
// 獲取自定義的注解,用于合并單元格
CustomMerge customMerge = theField.getAnnotation(CustomMerge.class);

// 沒有@CustomMerge注解的默認不合并
if (null == customMerge) {
continue;
}

for (int index = 0; index < fields.length; index++) {
Cell theCell = titleRow.getCell(index);
// 當配置為不需要導出時,返回的為null,這里作一下判斷,防止NPE
if (null == theCell) {
continue;
}
// 將字段和excel的表頭匹配上
if (easyExcelAnno.value()[0].equalsIgnoreCase(theCell.getStringCellValue())) {
if (customMerge.isPk()) {
pkIndex = index;
}

if (customMerge.needMerge()) {
needMergeColumnIndex.add(index);
}
}
}
}

// 沒有指定主鍵,則異常
if (null == this.pkIndex) {
throw new IllegalStateException("使用@CustomMerge注解必須指定主鍵");
}

}
}

  • 接下來在Controller中添加導出訂單列表的接口,將我們自定義的合并策略CustomMergeStrategy給注冊上去;

/**
* EasyExcel導入導出測試Controller
* Created by macro on 2021/10/12.
*/
@Controller
@Api(tags = "EasyExcelController", description = "EasyExcel導入導出測試")
@RequestMapping("/easyExcel")
public class EasyExcelController {

@SneakyThrows
@ApiOperation(value = "導出訂單列表Excel")
@RequestMapping(value = "/exportOrderList", method = RequestMethod.GET)
public void exportOrderList(HttpServletResponse response) {
List<Order> orderList = getOrderList();
List<OrderData> orderDataList = convert(orderList);
setExcelRespProp(response, "訂單列表");
EasyExcel.write(response.getOutputStream())
.head(OrderData.class)
.registerWriteHandler(new CustomMergeStrategy(OrderData.class))
.excelType(ExcelTypeEnum.XLSX)
.sheet("訂單列表")
.doWrite(orderDataList);
}
}

  • 在Swagger中訪問接口測試,導出訂單列表對應Excel;


SpringBoot 實現 Excel 導入導出,性能爆表,用起來夠優雅(springboot核心注解)

  • 下載完成后,查看下文件,由于EasyExcel需要自己來實現,對比之前使用EasyPoi來實現麻煩了不少。


SpringBoot 實現 Excel 導入導出,性能爆表,用起來夠優雅(springboot核心注解)

其他使用

由于EasyExcel的官方文檔介紹的比較簡單,如果你想要更深入地進行使用的話,建議大家看下官方Demo。


SpringBoot 實現 Excel 導入導出,性能爆表,用起來夠優雅(springboot核心注解)

總結

體驗了一把EasyExcel,使用還是挺方便的,性能也很優秀。但是比較常見的一對多導出實現比較復雜,而且功能也不如EasyPoi 強大。如果你的Excel導出數據量不大的話,可以使用EasyPoi,如果數據量大,比較在意性能的話,還是使用EasyExcel吧。

SpringBoot 實現 Excel 導入導出,性能爆表,用起來夠優雅(springboot核心注解)
本文原作者為陳嫣然,轉載請注明:出處!如該文有不妥之處,請聯系站長刪除,謝謝合作~

原創文章,作者:陳嫣然,如若轉載,請注明出處:http://www.uuuxu.com/20220516337784.html

主站蜘蛛池模板: 于田县| 格尔木市| 新巴尔虎右旗| 陆河县| 新干县| 鄂尔多斯市| 米泉市| 墨玉县| 锡林郭勒盟| 沈阳市| 奉贤区| 盐亭县| 大足县| 昂仁县| 内乡县| 中方县| 宁都县| 宜阳县| 东丰县| 乌拉特后旗| 多伦县| 双柏县| 诏安县| 普格县| 尼玛县| 密山市| 黄大仙区| 垣曲县| 中牟县| 大安市| 烟台市| 韶山市| 博野县| 武清区| 牡丹江市| 泰州市| 左云县| 沙雅县| 荥阳市| 内黄县| 平泉县|