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

如何在Java中异步执行存储过程?解决批量数据验证超时问题

问题

业务流程:用户上传Excel文件,文件内容存入SQL Server的表中,数据插入完成后执行验证存储过程校验数据。但当文件数据超过1500行时,验证存储过程运行时间过长,导致前端流程超时。已尝试增加存储过程执行超时时间但无效,已知存储过程本身需要优化,现寻求Java代码层面的改进方案。

验证完成后,会将验证结果写入原Excel文件并返回给用户。

相关代码如下:

数据存储函数

public void storeData(Sheet sheet) {
        
    int rowNum = sheet.getLastRowNum();

    String rowCount;   
    Connection conn = null;
    int batchSize = 1000;
    
    try {
        conn = SqlDBConnect.getConnection("DATADB");
        
        Statement s = conn.createStatement();
        s.executeUpdate("DELETE FROM [dbo].[TableA]");
        
        String insertQuery = "INSERT INTO [dbo].[TableA] ([ID],[NAME1],[NAME2],[COUNTRY],[Category1],[Category2],[Category3],[LEVEL]) VALUES (?,?,?,?,?,?,?,?)";
        
        PreparedStatement pstmt = conn.prepareStatement(insertQuery);
        pstmt.setQueryTimeout(3600);
        
        for(int i = 8; i <= rowNum; i++) 
        {
            Row row = sheet.getRow(i);
            for(int j = 0; j <= 10; j++)
            {  
                Cell cell = row.getCell(j,Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
                int columnIndex = cell.getColumnIndex();
    
                switch (columnIndex) {
                    case 0:
                        String Id = null;
                        Object obj = getCellValue(cell);
                        if(obj.getClass().toString().contains("java.lang.Double"))
                        {
                            Id = String.valueOf((double)obj);
                            Id = Id.substring(0,Id.length() - 2);
                        }
                        else {
                            Id = obj.toString();
                        }
                        pstmt.setString(1, Id);
                        break;
                    case 1:
                        pstmt.setString(2, getCellValue(cell).toString());
                        break;
                    case 2:
                        pstmt.setString(3, getCellValue(cell).toString());
                        break;
                    case 3:
                        pstmt.setString(4, getCellValue(cell).toString());
                        break;
                    case 4:
                        pstmt.setString(5, getCellValue(cell).toString());
                        break;
                    case 5:
                        pstmt.setString(6, getCellValue(cell).toString());
                        break;
                    case 6:
                        pstmt.setString(7, getCellValue(cell).toString());
                        break;
                    case 7:
                        pstmt.setString(8, getCellValue(cell).toString());
                        break;
                    case 8:
                        break;
                    case 9:
                        break;
                }
            }  
            pstmt.addBatch();
            if(i % batchSize == 0)
            {
                pstmt.executeBatch();
            }
        }
        
        //Executing the remaining queries.
        pstmt.executeBatch();
        returnCode = DBConst.DB_SUCCESS;
        conn.commit();
        
        fuf = new FileUploadFactory();
        returnCode = fuf.executeValidation(userID); // This calls the Validation Stored Procedure
        rowCount = Integer.toString(rowNum - 8);
    
        switch (returnCode) {
            case DBConst.DB_SUCCESS:
                bean.setReturnCode(DBConst.DB_SUCCESS);
                bean.setMessage(DataList.getRowStatus()); 
                break;
            case DBConst.DB_FAIL:
                bean.setReturnCode(DBConst.DB_FAIL);
                bean.setMessage("Error storing file");
                break;
            case DBConst.DB_WARNING:
                bean.setReturnCode(DBConst.DB_WARNING);
                bean.setMessage("File stored with warnings"+ rowCount + " rows successfully loaded.");
                break;
        }
    } catch (Exception e) {
        bean.setReturnCode(DBConst.DB_FAIL);
        bean.setMessage("Error storing file");
        Configuration.getInstance().getTelemetry().trackException(e);
    }
}

验证存储过程调用函数

public int executeValidation(String userID) {
    int returnCode = -1;
    Connection conn = null;
        
    try {
        conn = SqlDBConnect.getConnection("DATADB");
                    
        String sql = "EXEC [dbo].[Data_ImportValidation] ? ;";
        PreparedStatement s = conn.prepareStatement(sql);
        s.setEscapeProcessing(true);
        s.setQueryTimeout(3600);
        s.setString(1, userID);
        s.execute();
        conn.commit();
        returnCode = DBConst.DB_SUCCESS;         
    }
    catch (Exception e) {
        returnCode = DBConst.DB_FAIL;
        Configuration.getInstance().getTelemetry().trackException(e);       
    } finally {
        if(conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                Configuration.getInstance().getTelemetry().trackException(e);
            } 
        }
    }
              
    return returnCode;
}

Java代码层面优化建议

1. 异步执行验证流程

当前验证是同步阻塞的,直接导致前端超时。改为异步执行,先给前端返回"上传完成,验证中"的响应,后续通过轮询或WebSocket通知用户结果:

  • 用ExecutorService创建线程池,将验证任务提交到异步线程
  • 验证完成后生成带结果的Excel,存储到临时位置,记录任务状态
  • 前端根据任务状态查询结果,完成后下载文件

示例修改:

// 在storeData中替换同步验证的代码
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(() -> {
    try {
        fuf = new FileUploadFactory();
        int validationCode = fuf.executeValidation(userID);
        // 处理验证结果,生成带标记的Excel文件
        // 记录任务状态到数据库/缓存,比如用户ID对应验证状态和文件路径
    } catch (Exception e) {
        Configuration.getInstance().getTelemetry().trackException(e);
    } finally {
        executor.shutdown();
    }
});

// 立即返回前端响应,无需等待验证完成
bean.setReturnCode(DBConst.DB_SUCCESS);
bean.setMessage("文件已上传,正在执行验证,请稍后查询结果");

2. 复用数据库连接

当前storeData和executeValidation各自创建新连接,增加了连接开销。修改验证方法支持传入现有连接:

// 修改executeValidation方法,接受外部连接
public int executeValidation(String userID, Connection conn) throws SQLException {
    int returnCode = -1;          
    String sql = "EXEC [dbo].[Data_ImportValidation] ? ;";
    try (PreparedStatement s = conn.prepareStatement(sql)) {
        s.setEscapeProcessing(true);
        s.setQueryTimeout(3600);
        s.setString(1, userID);
        s.execute();
        returnCode = DBConst.DB_SUCCESS;         
    } catch (Exception e) {
        returnCode = DBConst.DB_FAIL;
        Configuration.getInstance().getTelemetry().trackException(e);       
    }
    return returnCode;
}

在storeData中直接传入已有的连接:

returnCode = fuf.executeValidation(userID, conn);

3. 优化Excel读取逻辑

  • 内层循环遍历0-10列但只处理0-7列,直接遍历目标列索引减少无效循环
  • 缓存单元格值,避免重复调用getCellValue
  • 简化ID转换逻辑,避免字符串截取:
// 替换原ID处理代码
Object obj = getCellValue(cell);
String Id = obj instanceof Number ? String.valueOf(((Number)obj).longValue()) : obj.toString();
pstmt.setString(1, Id);

4. 拆分事务范围

当前删除+插入+验证在同一事务中,数据量大时事务持续时间过长。拆分事务:

  • 删除表数据后单独提交事务
  • 批量插入分批次提交,减小单事务数据量
  • 验证过程单独处理,不与插入事务绑定

5. 增加异步任务的超时与监控

  • 给异步验证任务设置超时,避免无限等待:
Future<Integer> future = executor.submit(() -> fuf.executeValidation(userID));
try {
    int validationCode = future.get(30, TimeUnit.MINUTES); // 设置合理超时时间
    // 处理验证结果
} catch (TimeoutException e) {
    future.cancel(true);
    // 记录超时日志,标记任务为超时状态通知用户
}
  • 增加验证过程的日志,记录开始/结束时间、处理行数,便于排查瓶颈

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 17:44:50