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

Spring Boot+Hibernate批量插入大文件数据时触发OutOfMemoryError问题求助

嘿,这个批量插入导致OOM的场景我之前踩过不少坑,结合你的情况,给你几个针对性的解决方案,应该能解决问题:

核心问题根源

你遇到的本质问题是大事务下Hibernate Session缓存堆积:整个300万行处理都在同一个@Transactional事务里,Hibernate会把所有持久化的实体对象存在一级缓存(Session)中,随着处理行数增加,内存被这些对象占满,最终在190万行触发OOM。

具体解决方案

1. 批量清理Session缓存(最优先尝试)

每处理固定行数的记录后,手动触发缓存刷新并清空,避免对象堆积。配合Hibernate的批量配置效果更好:

  • 调整subFunction的处理逻辑,加入批量清理逻辑:
    @Autowired
    private EntityManager entityManager;
    
    public void subFunction() {
        int batchSize = 1000; // 可根据服务器配置调整,比如5000
        int count = 0;
        try (BufferedReader br = Files.newBufferedReader(Paths.get("your-large-file.txt"))) {
            String line;
            while ((line = br.readLine()) != null) {
                // 解析行数据为实体对象
                YourEntity entity = parseLineToEntity(line);
                entityManager.persist(entity);
                
                count++;
                if (count % batchSize == 0) {
                    entityManager.flush(); // 将缓存中的SQL提交到数据库
                    entityManager.clear(); // 清空Session缓存,释放内存
                }
            }
            // 处理最后一批剩余数据
            entityManager.flush();
            entityManager.clear();
        } catch (IOException e) {
            // 异常处理
        }
    }
    
  • 同时在application.properties开启Hibernate批量支持:
    spring.jpa.properties.hibernate.jdbc.batch_size=1000
    spring.jpa.properties.hibernate.order_inserts=true
    spring.jpa.properties.hibernate.order_updates=true
    
    这些配置让Hibernate把多个插入语句合并成批量SQL,减少数据库交互次数,进一步降低内存压力。

2. 拆分大事务为小事务

如果单个事务处理300万行数据,不仅占内存,还会导致数据库事务日志过大、锁表时间过长等问题。可以改用编程式事务,每处理一批就提交一个小事务:

@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private EntityManager entityManager;

public void subFunction() {
    int batchSize = 1000;
    int count = 0;
    TransactionStatus status = null;
    
    try (BufferedReader br = Files.newBufferedReader(Paths.get("your-large-file.txt"))) {
        String line;
        while ((line = br.readLine()) != null) {
            // 每批开始时开启新事务
            if (count % batchSize == 0) {
                if (status != null) {
                    transactionManager.commit(status);
                }
                DefaultTransactionDefinition def = new DefaultTransactionDefinition();
                status = transactionManager.getTransaction(def);
            }
            
            YourEntity entity = parseLineToEntity(line);
            entityManager.persist(entity);
            count++;
        }
        // 提交最后一批事务
        if (status != null) {
            transactionManager.commit(status);
        }
    } catch (IOException e) {
        if (status != null) {
            transactionManager.rollback(status);
        }
        // 异常处理
    }
}

3. 绕开Hibernate,用原生JDBC批量插入

如果Hibernate的ORM层还是带来额外内存开销,直接用JDBC的PreparedStatement做批量插入,完全避开Session缓存:

@Autowired
private DataSource dataSource;

public void subFunction() {
    String insertSql = "INSERT INTO your_table (col1, col2, col3) VALUES (?, ?, ?)";
    int batchSize = 1000;
    int count = 0;
    
    try (Connection conn = dataSource.getConnection();
         PreparedStatement pstmt = conn.prepareStatement(insertSql)) {
        conn.setAutoCommit(false);
        
        try (BufferedReader br = Files.newBufferedReader(Paths.get("your-large-file.txt"))) {
            String line;
            while ((line = br.readLine()) != null) {
                // 解析行数据并设置参数
                String[] fields = line.split(",");
                pstmt.setString(1, fields[0]);
                pstmt.setString(2, fields[1]);
                pstmt.setInt(3, Integer.parseInt(fields[2]));
                
                pstmt.addBatch();
                count++;
                
                if (count % batchSize == 0) {
                    pstmt.executeBatch();
                    conn.commit();
                }
            }
            // 执行最后一批
            pstmt.executeBatch();
            conn.commit();
        }
    } catch (SQLException | IOException e) {
        // 异常处理
    }
}

这种方式内存占用最小,适合超大量数据的插入场景。

4. 结合MAT分析的针对性优化

既然你已经用Eclipse Memory Analyzer分析了内存转储,还可以针对性优化:

  • 如果内存中占比最高的是实体对象或Hibernate缓存对象:那就是缓存没清理的问题,用方案1或2解决。
  • 如果是文件读取相关对象(比如字符串、字节数组):优化文件读取方式,用流式读取(比如上面代码里的BufferedReader逐行读),不要一次性把整个文件加载到内存(比如避免用Files.readAllLines())。
额外建议

可以适当调整JVM堆内存参数(比如-Xmx4g,根据服务器硬件配置调整),但这只是治标,核心还是解决对象堆积的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 14:33:13