JHipster+Angular5+SpringBoot生产环境下载大文件报OutOfMemoryError
解决大Blob文件下载OutOfMemoryError的方案
这个问题我之前碰到过好几次,本质就是Hibernate默认会把Blob数据一次性加载到JVM内存中,35M的文件直接超出了当前堆内存的可用空间,或者触发了Hibernate内部数组扩容的上限(你推测的2M初始数组就是这个原因)。给你几个循序渐进的解决办法:
1. 优先用流式下载,避免一次性加载整个Blob
这是最直接的修复方式,核心思路是从数据库获取Blob的输入流,然后直接把流写入HTTP响应输出流,全程不把整个文件加载到内存里。
Spring Boot后端代码示例
@GetMapping("/download/{fileId}") public void downloadFile(@PathVariable Long fileId, HttpServletResponse response) throws IOException { // 从数据库查询文件实体(注意不要直接加载Blob字段,用懒加载或者投影) FileEntity file = fileRepository.findById(fileId).orElseThrow(() -> new RuntimeException("File not found")); // 设置响应头 response.setContentType(file.getContentType()); response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFileName() + "\""); response.setContentLengthLong(file.getFileSize()); // 提前存在数据库的文件大小 // 获取Blob的输入流,直接写入响应输出流 try (InputStream inputStream = file.getBlob().getBinaryStream(); OutputStream outputStream = response.getOutputStream()) { byte[] buffer = new byte[4096]; // 用4KB缓冲区,避免大内存占用 int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } outputStream.flush(); } catch (SQLException e) { throw new IOException("Failed to read blob data", e); } }
关键注意点
- 确保你的
FileEntity中Blob字段用@Lob和FetchType.LAZY标注,避免查询时自动加载整个Blob:@Lob @Basic(fetch = FetchType.LAZY) private Blob fileBlob; - 一定要提前把文件大小存在数据库(比如
fileSize字段),这样可以设置Content-Length头,让前端知道下载进度。
2. 调整Hibernate的Blob处理配置
如果流式下载还不行,可以尝试修改Hibernate的Blob相关配置,让它支持更大的数组扩容:
- 在
application.yml中添加:spring: jpa: properties: hibernate: jdbc: lob: non_contextual_creation: true max_length: 41943040 # 设置40M的最大长度,对应你的35M文件
不过这个方案治标不治本,因为如果以后有更大的文件还是会出问题,优先推荐流式下载。
3. 长远方案:换用文件系统/对象存储存储大文件
数据库存大Blob本来就不是最优实践,会拖累数据库性能,也容易引发内存问题。JHipster其实有现成的文件存储支持,你可以:
- 把大文件存在服务器本地文件系统或者云OSS(比如阿里云OSS、AWS S3)
- 数据库只存储文件的路径、文件名、大小、哈希值等元数据
- 下载时直接从文件系统/OSS读取流返回给前端,彻底避免数据库Blob的内存问题
Angular端配合处理
Angular5下载大文件时,不要用HttpClient默认的响应处理,建议用responseType: 'blob'然后创建下载链接:
downloadFile(fileId: number): void { this.http.get(`/api/download/${fileId}`, { responseType: 'blob' }) .subscribe(blob => { const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'your-filename.ext'; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); }); }
内容的提问来源于stack exchange,提问作者Jeya Balaji
相关产品推荐
相关产品推荐

