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

 更新时间:2024年04月19日 15:36:12   作者:偷代码的猫  
这篇文章主要介绍了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、读取写入文件

  • @Transactional(readOnly = true):搭配数据库Cursor查询使用,事务注解为只读,保证数据整体的一致性
  • fieldArr:字段数组,规定要写入文件的字段项,比如查询的全部字段为a,b,c,d,而我只想显示a,c,d
  • headerArr:字段名数组,对应文件标题栏,需要不字段数组a,c,d名称一一对应
  • CsvUtils:一个简单的工具类,后续贴代码
/**
   * 导出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();
  }
}

总结

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

相关文章

  • 基于java实现一个脱敏组件

    基于java实现一个脱敏组件

    这篇文章主要为大家详细介绍了如何基于java实现一个脱敏组件,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-11-11
  • Java详解线上内存暴涨问题定位和解决方案

    Java详解线上内存暴涨问题定位和解决方案

    本篇文章介绍了我在开发过程中遇到的线上内存暴涨的问题,以及定位问题原因和解决该问题的过程及思路,通读本篇对大家的学习或工作具有一定的价值,需要的朋友可以参考下
    2021-10-10
  • IDEA创建SpringBoot的五种方式

    IDEA创建SpringBoot的五种方式

    在软件开发的浩瀚海洋中,Spring Boot以其独特的魅力和强大的功能,为开发者开辟了一条通往高效、便捷开发之路,本文旨在给大家介绍IDEA创建SpringBoot的五种方式,并通过代码和图文介绍的非常详细,需要的朋友可以参考下
    2025-03-03
  • 详解Java数据库连接JDBC基础知识(操作数据库:增删改查)

    详解Java数据库连接JDBC基础知识(操作数据库:增删改查)

    这篇文章主要介绍了详解Java数据库连接JDBC基础知识(操作数据库:增删改查),本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-01-01
  • Spring Boot读取自定义配置文件

    Spring Boot读取自定义配置文件

    在Spring Boot项目中我们经常需要读取application.yml配置文件的自定义配置,今天就来罗列一下从yaml读取配置文件的一些常用手段和方法。
    2021-05-05
  • 一篇文章带你了解Java基础-接口

    一篇文章带你了解Java基础-接口

    这篇文章主要介绍了java接口基础知识,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-08-08
  • Java中数组越界异常的优雅解决方式

    Java中数组越界异常的优雅解决方式

    ‌数组越界报错通常发生在尝试访问数组中不存在的索引时,这可能导致程序崩溃或异常,这篇文章主要给大家介绍了关于Java中数组越界异常的优雅解决方式,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-07-07
  • Springboot项目因为kackson版本问题启动报错解决方案

    Springboot项目因为kackson版本问题启动报错解决方案

    这篇文章主要介绍了Springboot项目因为kackson版本问题启动报错解决方案,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-07-07
  • java虚拟机指令dup详解

    java虚拟机指令dup详解

    这篇文章主要为大家详细介绍了java虚拟机指令dup,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-10-10
  • Springboot异常日志输出方式

    Springboot异常日志输出方式

    这篇文章主要介绍了Springboot异常日志输出方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12

最新评论