You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

处理超10GB数据时Spring Boot API堆内存耗尽问题求解

解决Spring Boot返回超大数据量导致内存溢出的方案

针对你用Spring Boot 2.7返回2000万条、10GB+数据到Angular 6时出现的内存溢出问题,以下是无需扩容JVM的可行方案:

1. 流式返回(边查边输出,不存全量数据)

核心思路是不把所有数据加载到内存集合中,而是直接将数据库查询到的每一行数据写入HTTP响应流,实现边查边返回。

后端实现示例

用Spring的StreamingResponseBody配合JDBC流式查询:

@GetMapping("/large-data-stream")
public ResponseEntity<StreamingResponseBody> streamLargeData() {
    StreamingResponseBody responseBody = outputStream -> {
        // 使用JdbcTemplate流式查询,避免全量加载
        jdbcTemplate.query("SELECT id, name, required_field FROM large_table", 
            new RowCallbackHandler() {
                @Override
                public void processRow(ResultSet rs) throws SQLException {
                    // 将单行数据转为JSON(可自定义转换逻辑)
                    JSONObject json = new JSONObject();
                    json.put("id", rs.getLong("id"));
                    json.put("name", rs.getString("name"));
                    json.put("requiredField", rs.getString("required_field"));
                    
                    try {
                        // 写入响应流,每行一个JSON
                        outputStream.write(json.toString().getBytes(StandardCharsets.UTF_8));
                        outputStream.write('\n');
                    } catch (IOException e) {
                        throw new RuntimeException("写入流失败", e);
                    }
                }
            });
    };

    return ResponseEntity.ok()
            .contentType(MediaType.parseMediaType("application/x-ndjson")) // 换行分隔JSON格式
            .body(responseBody);
}

注意:如果用JPA,需开启事务并使用Stream<T>查询(repository.findAll(Streamable.of(Pageable.unpaged()))),同时关闭Hibernate的自动缓存,避免数据被加载到内存。

前端Angular处理

通过流式接收响应,逐行解析数据:

import { HttpClient } from '@angular/common/http';

constructor(private http: HttpClient) {}

loadStreamingData() {
  this.http.get('/api/large-data-stream', { 
    responseType: 'blob', 
    observe: 'response' 
  }).subscribe(response => {
    const reader = response.body!.getReader();
    const decoder = new TextDecoder('utf-8');
    let buffer = '';

    const processChunk = (result: ReadableStreamDefaultReadResult<Uint8Array>) => {
      if (result.done) {
        console.log('数据接收完成');
        return;
      }
      buffer += decoder.decode(result.value, { stream: true });
      const lines = buffer.split('\n');
      buffer = lines.pop() || ''; // 保留未完成的行

      lines.forEach(line => {
        if (line.trim()) {
          const data = JSON.parse(line);
          // 将数据添加到前端列表或做业务处理
          console.log('接收数据:', data);
        }
      });

      return reader.read().then(processChunk);
    };

    reader.read().then(processChunk);
  });
}

2. 分页查询+前端滚动加载

将全量数据拆分为多页,前端每次仅请求当前需要的一页数据(比如每页1000-5000条),滚动到页面底部时再请求下一页,彻底避免前后端加载全量数据。

后端实现(Spring Data JPA)

@GetMapping("/page-data")
public Page<YourEntity> getPagedData(
    @RequestParam(defaultValue = "0") int page,
    @RequestParam(defaultValue = "1000") int size) {
    // 分页查询,自动处理数据库层面的limit/offset
    return yourEntityRepository.findAll(PageRequest.of(page, size));
}

前端Angular实现

监听滚动事件,按需加载下一页:

private currentPage = 0;
private pageSize = 1000;
public dataList: YourEntity[] = [];

constructor(private http: HttpClient) {}

ngOnInit() {
  this.loadNextPage();
  // 监听滚动事件(示例:window滚动)
  window.addEventListener('scroll', () => {
    if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 100) {
      this.loadNextPage();
    }
  });
}

loadNextPage() {
  this.http.get<Page<YourEntity>>(`/api/page-data?page=${this.currentPage}&size=${this.pageSize}`)
    .subscribe(page => {
      this.dataList.push(...page.content);
      if (page.hasNext()) {
        this.currentPage++;
      } else {
        console.log('无更多数据');
      }
    });
}

// 定义Page接口,匹配后端返回结构
interface Page<T> {
  content: T[];
  number: number;
  size: number;
  totalElements: number;
  hasNext(): boolean;
}

3. 数据压缩传输

开启HTTP响应压缩,大幅减少传输数据量,同时降低后端内存占用(压缩后的数据流更小,内存缓存压力降低)。

在Spring Boot的application.yml中配置:

server:
  compression:
    enabled: true
    mime-types: application/json,application/xml,text/html,text/xml,text/plain
    min-response-size: 1024 # 超过1KB的响应才压缩

Angular前端会自动识别并解压Gzip压缩的响应,无需额外代码。

4. 裁剪返回字段

只返回前端实际需要的字段,减少单条数据的大小:

  • 用DTO(数据传输对象)替代实体类,仅包含前端需要的属性;
  • 或在实体类中用@JsonIgnore标记不需要返回的字段;
  • 或在SQL查询中仅选择需要的列(比如SELECT id, name FROM table而非SELECT *)。

5. 文件导出替代接口返回

如果前端不需要实时展示所有数据,而是用于导出分析,可将数据导出为CSV/Excel文件,返回下载链接而非直接返回数据:

  • 用Apache POI的SXSSFWorkbook处理大Excel(流式写入,避免内存溢出);
  • 用CSVWriter流式写入CSV文件;
  • 后端生成文件后,返回文件下载URL,前端触发下载即可。

内容的提问来源于stack exchange,提问作者Loki

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 09:25:57