EasyExcel封装一个分页写数据的通用方法(保姆级),继上一篇easyExcel导出上线后的优化

Posted 路过人间的姜先生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了EasyExcel封装一个分页写数据的通用方法(保姆级),继上一篇easyExcel导出上线后的优化相关的知识,希望对你有一定的参考价值。

【EasyExcel】封装一个分页写数据的通用方法

需求:通过elasticsearch查询出来一次性写,在大数据量时存在OOM的隐患分页查询、分批次写数据,避免导出大数据量时内存消耗陡增基于elasticsearch分页查询;mybatis-puls同理


文章目录

前言

在上个博客中解决了线上导出字体依赖的问题,由于涉及的导出模块较多,因为打算封装一个方法做通用导出。

一、所需依赖

1、easyexcel mavn 依赖文件

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.1.1</version>
            <optional>true</optional>
        </dependency>

二、源码实现

1、依赖的枚举类:ExcelPageEnum(自定义)

ExcelPageEnum:

/**
 * @className: ExcelPageEnum
 * @description: 存储 XLS与XLSX每次最大写入与sheet最大Row的枚举
 * @author: 云野
 * @date: 2022/8/12
 */
public enum ExcelPageEnum 
  /**
   * 
   * default export xlsx, page size 10000, sheet max row 1000000
   * @author: 云野
   * @date: 2022/8/12
   */
  XLS(10_000, 60_000),
  XLSX(10_000, 1_000_000);

  private int pageSize;
  private int sheetMaxRow;

  ExcelPageEnum(int pageSize, int sheetMaxRow) 
    this.pageSize = pageSize;
    this.sheetMaxRow = sheetMaxRow;
  

  public int getPageSize() 
    return pageSize;
  

  public int getSheetMaxRow() 
    return sheetMaxRow;
  


2、依赖的核心接口类:PageQueryService(自定义)

/**
 * @className: PageQueryService
 * @description: 实现通用的核心接口
 * @author: 云野
 * @date: 2022/8/12
 */
@FunctionalInterface
public interface PageQueryService<E> 

  /**
   * 带条件分页查询所有数据
   *
   * @return Result<List>
   */
  List<E> findByPage(Integer current, Integer size);


3、最终调用工具类: EasyExcelUtils

/**
 * 通用导出方法
 *
 * @className: EasyExcelUtils
 * @author: 云野
 * @date: 2022/8/12
 */
@Slf4j
@Component
public class EasyExcelUtils extends EasyExcel 

  public static void pageWrite(
      ExcelWriter excelWriter,
      String sheetName,
      Integer totalCount,
      HttpServletResponse response,
      PageQueryService pageQueryService) 

    // default export xlsx, page size 10000, sheet max row 1000000
    int pageSize = ExcelPageEnum.XLSX.getPageSize();
    int sheetMaxRow = ExcelPageEnum.XLSX.getSheetMaxRow();

    ExcelTypeEnum excelType = excelWriter.writeContext().writeWorkbookHolder().getExcelType();
    boolean isXls = excelType != null && ExcelTypeEnum.XLS.getValue().equals(excelType.getValue());
    if (isXls) 
      pageSize = ExcelPageEnum.XLS.getPageSize();
      sheetMaxRow = ExcelPageEnum.XLS.getSheetMaxRow();
    

    try 
      // 下方使用了用户自己选择文件保存路径的方式,所以需要配请求参数,如果使用固定路径可忽略此代码
      String filename = URLEncoder.encode(sheetName + ".xlsx", "UTF-8");
      response.setCharacterEncoding("UTF-8");
      // 设定输出文件头
      response.setHeader("Content-disposition", "attachment; filename=" + filename);
      // 定义输出类型
      response.setContentType("application/x-xls");

      // 这里其实就是把上面的方法分开写,写入同一个sheet
      WriteSheet writeSheet = EasyExcelFactory.writerSheet(sheetName).build();

      // compute page count, sheet count
      long pageCount = (totalCount - 1) / pageSize + 1;
      long sheetCount = (totalCount - 1) / sheetMaxRow + 1;
      int currentPage = 0;

      // page write data
      WriteSheet sheet = null;
      for (int i = 0; i < sheetCount; i++) 
        Pageable pageable = new PageRequest(i, pageSize);
        sheet = EasyExcelFactory.writerSheet(i, sheetName + i).build();
        for (int j = 0; j < (sheetMaxRow / pageSize); j++) 
          excelWriter.write(pageQueryService.findByPage(currentPage, pageSize), sheet);
          currentPage++;
          if (currentPage >= pageCount) 
            break;
          
        
      
     catch (Exception e) 
      e.printStackTrace();
     finally 
      if (excelWriter != null) 
        excelWriter.finish();
      
    
  

三、代码调用方式

1、Controller层代码

@Slf4j
@Api(tags = "后台--货源服务API")
@RestController
@RequestMapping("/api/admin")
public class AdminCargoOwnerSupplyResource 

  @Autowired private HttpServletResponse response;
  @Autowired private CargoOwnerSupplyService cargoOwnerSupplyService;
  
  /**
   * @author: 云野 @Description: 导出货源Excel
   * @date: 2022/8/12
   * @param: [queryRequest]查询条件对象
   */
  @ApiOperation(value = "导出货源", httpMethod = "GET")
  @GetMapping(value = "/url")
  public void exportExcel(AdminCargoOwnerSupplyQueryRequest queryRequest) 

    ServletOutputStream outputStream = null;
    try 
      outputStream = response.getOutputStream();
     catch (IOException e) 

    
    // build excel writer , CargoOwnerSupplyExportExcel 为自定义的导出类
    // excelType:导出类型,我这边导出的是XLSX文件
    ExcelWriter excelWriter =
        EasyExcelFactory.write(outputStream, CargoOwnerSupplyExportExcel.class)
            .excelType(ExcelTypeEnum.XLSX)
            .build();
            
    // page write  ; “CargoOwnerSupply”为写出的文件名字
    // cargoOwnerSupplyService.countOfQueryRequest(queryRequest) 是调用的方法,查询需要导出多少数据
    EasyExcelUtils.pageWrite(
        excelWriter,
        "CargoOwnerSupply",
        cargoOwnerSupplyService.countOfQueryRequest(queryRequest),
        response,
        (currentPage, pageSize) ->
            cargoOwnerSupplyService.exportExcel(
                queryRequest, new PageRequest(currentPage, pageSize)));
  


2、ExportExcel导出层实体(自定义,也可以是直接的entity实体)

/**
 * @Author 云野 @Description: 导出Excel后台货源 @Date 2022/8/12
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CargoOwnerSupplyExportExcel 
  /** 账号 */
  @ExcelProperty(value = "账号", index = 0)
  private String account;

  /** 联系人 */
  @ExcelProperty(value = "联系人", index = 1)
  private String contactPerson;

  /** 发布时间 */
  @ExcelProperty(value = "发布时间", index = 2)
  private String createTime;

  /** 货物名称 */
  @ExcelProperty(value = "货物名称", index = 4)
  private String goodsName;

  /** 发货量 */
  @ExcelProperty(value = "发货量", index = 5)
  private Integer deliveryMount;


3、Service接口层

/** @Author 云野 @Description: @Date 2022/8/12 */
public interface CargoOwnerSupplyService 

  /** 根据条件查询货源数量 */
  Integer countOfQueryRequest(AdminCargoOwnerSupplyQueryRequest request);

  /**
   * 导出Excel
   *
   * @param request
   * @param pageable
   */
  List<CargoOwnerSupplyExportExcel> exportExcel(
      AdminCargoOwnerSupplyQueryRequest request, Pageable pageable);


4、Service接口实现层

/** @Author 云野 @Description: @Date 2022/8/12 */
public interface CargoOwnerSupplyServiceImpl 

  /** 根据条件查询货源数量 */
    @Override
  public Integer countOfQueryRequest(AdminCargoOwnerSupplyQueryRequest request) 
    // 这是是我Elasticsearch的查询数量的方法,如果是sql的话,用自己的api
    return Integer.valueOf(
        String.valueOf(
            elasticsearchTemplate.count(
                new NativeSearchQueryBuilder().withQuery(queryConditions(request)).build(),
                CargoOwnerSupplyDTO.class)));
  

  /**
   * 
   * 因为我没有直接使用数据库查询出来的entity实体对象,而是再封装定义了一层ExportExcel实体对象,因此我这边需要准备这个赋值的方式,如果看官们直接用的数据库查询出来的 entity实体,那么就不需要赋值的过程
   * @author: 云野 
   * @Description: 导出Excel
   * @date: 2022/8/12
   * @param: [request(查询条件), pageable(分页查询)]
   * @return: void
   */
  @Override
  public List<CargoOwnerSupplyExportExcel> exportExcel(
      AdminCargoOwnerSupplyQueryRequest request, Pageable pageable) 

    // 通过条件从数据库查询所有,返回一个List
    List<CargoOwnerSupplyVo> cargoOwnerSupplyList =
        cargoOwnerSupplyWebMapper.dtos2vos(adminFindAll(request, pageable).getContent());

    // 初始化List
    ArrayList<CargoOwnerSupplyExportExcel> list = new ArrayList<>();
    // 遍历查询出来的 List<CargoOwnerSupplyVo> , 然后进行赋值到 ArrayList<CargoOwnerSupplyExportExcel>的操作
    cargoOwnerSupplyList.stream()
        .forEach(
            vo -> 
              // 基于@Builder注解进行赋值
              CargoOwnerSupplyExportExcel excel = CargoOwnerSupplyExportExcel
              .builder()
              .account(vo.getContactPhone())
              .contactPerson(vo.getContactPerson())
              .createTime(vo.getCreateTime())
              .goodsName(vo.getGoodsName())
              .deliveryMount(vo.getDeliveryMount()).build();
     
              list.add(excel);
            );
    return list;
  


三、总结

看过我以前博客的小伙伴应该知道,我这边所有的案例都是开发时碰到的,同时我这边的Data层用的是Elasticsearch+Spring Data Jpa ,因为公司用的就是这个呀,所以案例中出现 的查询方案案例,如果使用Sql的小伙伴不需要进行深究,只需要明白一点就行,这种时候可以直接使用Sql中同样的查询即可。
看到这里如果帮助到了你,给个点赞,谢谢啦!

以上是关于EasyExcel封装一个分页写数据的通用方法(保姆级),继上一篇easyExcel导出上线后的优化的主要内容,如果未能解决你的问题,请参考以下文章

通用分页model封装pageList

EasyExcel导出基本使用,及作用于通用报表导出

使用amaze ui的分页样式封装一个通用的JS分页控件

MyBatis-Plus对于大数据量查询,采用分页查询按批次处理结果,通用工具封装

MyBatis-Plus对于大数据量查询,采用分页查询按批次处理结果,通用工具封装

MybatisPlus 快速构建MybatisPlus 原生mybatis(分页查询) 通用枚举 service 封装 自动填充