如何在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
相关产品推荐
相关产品推荐

