遭遇Hibernate异常后Spring @Scheduled定时任务停止运行问题求助
首先,你的定时任务在捕获死锁异常后停止调度,核心原因大概率是资源未正确释放导致Hibernate Session/连接池状态异常,再加上可能存在的Hibernate与hibernate-hikaricp版本不兼容问题,共同导致任务无法继续执行。
1. 最关键的问题:资源未正确释放
你的calculateKPIs方法没有用try-finally保证Session和Transaction的正确关闭/回滚。当死锁异常发生时,txApp.commit()和sessApp.close()根本不会执行,导致Session一直处于打开状态——虽然HikariCP最终检测到泄漏并回收了连接,但Hibernate内部的Session状态可能已经紊乱,后续调用openSession()时可能抛出未被你注意到的异常(或是日志配置问题没记录到),进而让任务看似“停止运行”。
修复资源释放的代码:
private void calculateKPIs() { Session sessApp = null; Transaction txApp = null; try { sessApp = HibernateUtilApp.getSessionFactory().openSession(); txApp = sessApp.beginTransaction(); Integer val = (Integer) sessApp.createSQLQuery("some query").uniqueResult(); txApp.commit(); } catch (Exception e) { // 异常时必须回滚事务,避免事务残留 if (txApp != null && txApp.isActive()) { try { txApp.rollback(); } catch (Exception rollbackEx) { logger.error("Failed to rollback transaction", rollbackEx); } } throw e; // 重新抛出让上层catch处理日志 } finally { // 确保Session始终被关闭,避免连接泄漏 if (sessApp != null && sessApp.isOpen()) { try { sessApp.close(); } catch (Exception closeEx) { logger.error("Failed to close Hibernate Session", closeEx); } } } }
2. 版本不兼容隐患
你使用的hibernate-hikaricp 6.0.0是为Hibernate 6.x设计的,而你的Hibernate核心版本是5.6.3,两者版本不匹配。这种跨大版本的依赖可能导致连接池整合逻辑出现异常,比如死锁后连接池无法正常重置连接,进而影响后续任务获取连接。
修复方案:
将hibernate-hikaricp的版本降级到与Hibernate核心一致的版本,比如5.6.3.Final,确保依赖版本完全匹配。
3. 死锁场景的重试机制
死锁是数据库层面的常见问题,应该在代码中加入重试逻辑,避免一次死锁就影响任务的持续运行。你可以在调度方法中增加重试次数:
@Scheduled(fixedDelay = 5000) public void runCalculations() { int maxRetries = 3; int retryCount = 0; boolean success = false; while (retryCount < maxRetries && !success) { try { this.calculateKPIs(); success = true; } catch (LockAcquisitionException | com.microsoft.sqlserver.jdbc.SQLServerException e) { retryCount++; logger.warn("Deadlock detected (retry {}/{}): {}", retryCount, maxRetries, e.getMessage()); // 重试前短暂等待,避免立即再次触发死锁 try { Thread.sleep(1000); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); break; } } catch (Throwable t) { logger.error("runCalculations() -> unexpected exception: {}", t.getMessage(), t); break; } } if (!success) { logger.error("runCalculations() -> failed after {} retries due to deadlocks", maxRetries); } }
4. 验证Spring调度器状态
虽然你说其他任务正常,但可以检查Spring调度线程池的配置,确保核心线程数足够,且没有因为未处理的异常导致任务被移除。另外,可以配置Spring的TaskScheduler错误处理器,确保任何未被捕获的异常都会被记录,避免“静默失败”:
@Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setPoolSize(10); scheduler.setThreadNamePrefix("scheduled-task-pool-"); scheduler.setErrorHandler(t -> logger.error("Scheduled task error occurred", t)); return scheduler; }
按照以上步骤修复后,你的定时任务应该能恢复正常调度,同时也能更好地处理死锁场景。
内容的提问来源于stack exchange,提问作者vojtak

