java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot查询数据库导出报表文件

SpringBoot查询数据库导出报表文件方式

作者:偷代码的猫

这篇文章主要介绍了SpringBoot查询数据库导出报表文件方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

一、背景

1、需求

几千万条报表数据导出到Excel中

2、问题

在数据量导出不大时,我们的常规做法是使用MySQL直接查询出全部数据,整理规划成Excel列表,使用POI写入到Excel文件中

但是当数据量较大时,使用MySQL查询出所有数据,一会超时断开连接,二会内存溢出,使用POI暂时不支持分布写入数据到Excel中

3、解决

使用数据库流式读取可以解决数据库读取时间过长,内存溢出问题,这个解决了一次性读取全部数据到内存中

使用CSV文件代替xlsx/xls文件写入,CSV也可以使用Excel打开操作,并且也可另存为xlsx/xls,CSV本质是文本文件,不用确定尾结点,可以如TXT一样持续向文件追加内容

二、代码实现

1、mapper文件

使用Cursor游标标识,流式查询数据数据

  @Select("<script>"
      + "select * from `user_report`"
      + "</script>")
  Cursor<UserReport> selectCursorByCondition();

2、读取写入文件

/**
   * 导出Excel
   * @param fieldArr       字段数组
   * @param headerArr      字段名数组
   * @param fileName       文件名
   */
  @Transactional(readOnly = true)
  public void exportList(HttpServletResponse response,
      String[] fieldArr, String[] headerArr, String fileName) {
    //设置文件格式
    response.setCharacterEncoding("UTF-8");
    response.addHeader("Content-Type", "application/csv");
    try {
       //文件名 设置为中文
      fileName = new String(fileName.getBytes("gb2312"), "iso8859-1");
    } catch (Exception e) {
      log.error("file name show error:{}", e.getMessage());
    }
    //响应头部
    response.addHeader("Content-Disposition", "attachment; filename=" + fileName + "(" +
        DateUtil.format(DateUtil.date(), DatePattern.NORM_DATETIME_PATTERN) + ").csv");
    //查询数据
    Cursor<UserReport> modelStream = xxMapper.selectCursorByCondition();
 
    try {
      PrintWriter out = response.getWriter();
      try {
        //写入标题行
        out.write(CsvUtils.getTitleLine(headerArr));
        //写入数据行
        modelStream.forEach(item -> {
          out.write(CsvUtils.getRowLine(fieldArr, item));
        });
        out.flush();
      } catch (Exception e) {
        log.error("write file error:{}", e.getMessage());
      } finally {
        out.close();
      }
    } catch (Exception e) {
      log.error("exportList error:{}", e.getMessage());
    }
 
 
  }

3、CsvUtils工具类

import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;
 
/**
 * @Author: catcoder
 * @Desc:
 * @Time: 10:05 2021/8/2
 **/
public class CsvUtils {
 
  /**
   * CSV文件列分隔符
   */
  public static final String CSV_COLUMN_SEPARATOR = ",";
 
  /**
   * CSV文件行分隔符
   */
  public static final String CSV_ROW_SEPARATOR = System.lineSeparator();
 
 
  /**
   * 获取标题行
   *
   * @param headerArr 标题数组
   * @return
   */
  public static String getTitleLine(String[] headerArr) {
    StringBuffer line = new StringBuffer("");
    for (String title : headerArr) {
      line.append(title).append(CSV_COLUMN_SEPARATOR); //添加标题行数据
    }
    line.append(CSV_ROW_SEPARATOR); //换行数据
    return line.toString();
  }
 
  /**
   * 或去数据行
   *
   * @param fieldArr 字段数组
   * @param obj      实体对象
   * @return
   */
  public static String getRowLine(String[] fieldArr, Object obj) {
    StringBuffer line = new StringBuffer("");
    Class<?> srcClass = obj.getClass();
 
    //获取Obj 所有字段
    Set<String> objFiled = new HashSet<>();
    Field[] fields = obj.getClass().getDeclaredFields();
    for (Field field : fields) {
      objFiled.add(field.getName());
    }
 
    for (String field : fieldArr) {
      try {
        // 获取对象对应的Field
        if (objFiled.contains(field)) {
          Field objField = srcClass.getDeclaredField(field);
          objField.setAccessible(true); //设置private可访问
          Object value = objField.get(obj);
          line.append(value); //添加元素
        }
      } catch (Exception e) {
        e.printStackTrace();
      } finally {
        line.append(CSV_COLUMN_SEPARATOR); //CSV间隔数据
      }
    }
    line.append(CSV_ROW_SEPARATOR);//换行数据
    return line.toString();
  }
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文