如何确保Oracle会话已关闭?Angular+Spring场景会话管理疑问
问题分析
首先,你看到的java.io.IOException: An established connection was aborted by the software in your host machine是正常现象——当Angular调用unsubscribe()主动断开请求时,Tomcat(或你使用的Servlet容器)会检测到客户端连接中断,抛出这个异常。但核心问题是:Spring端如果没有及时终止数据库操作,Oracle会话可能会一直占用直到存储过程执行完毕,甚至残留无效连接。
下面是一套完整的解决方案,从Spring请求中断处理、数据库连接池配置到Oracle会话验证,一步步帮你解决问题:
1. 在Spring中捕获请求取消事件,主动终止数据库操作
当客户端取消请求时,我们需要主动通知数据库停止正在执行的存储过程,避免会话长时间占用。这里推荐用Spring的WebAsyncTask来处理异步请求,它原生支持取消回调:
第一步:改造Controller,用WebAsyncTask处理请求
@RestController @RequestMapping("/api") public class YourEntityController { @Autowired private YourEntityService entityService; @PostMapping("/entity0") public WebAsyncTask<?> postEntity( @RequestParam Arg0 arg0, @RequestParam Arg1 arg1, @RequestParam Arg2 arg2, HttpServletRequest request) { // 生成唯一请求ID,用于后续Oracle会话追踪 String requestTraceId = UUID.randomUUID().toString(); Callable<Object> callable = () -> { // 执行长耗时存储过程 return entityService.executeStoredProcedure(arg0, arg1, arg2, requestTraceId); }; // 设置足够长的超时(比如2分钟,匹配你的API响应时长) WebAsyncTask<Object> asyncTask = new WebAsyncTask<>(120000L, callable); // 请求被取消时的回调:终止数据库操作 asyncTask.onCancel(() -> { entityService.cancelCurrentProcedure(); return ResponseEntity.status(HttpStatus.GONE).body("Request cancelled"); }); return asyncTask; } }
第二步:在Service层管理存储过程的执行与取消
我们需要保存CallableStatement的引用,以便在取消时调用cancel()方法终止数据库操作:
@Service public class YourEntityService { private CallableStatement currentStatement; @Autowired private DataSource dataSource; private static final Logger log = LoggerFactory.getLogger(YourEntityService.class); @Transactional public Object executeStoredProcedure(Arg0 arg0, Arg1 arg1, Arg2 arg2, String requestTraceId) throws SQLException { Connection connection = dataSource.getConnection(); try { // 设置Oracle会话标识,方便后续查询 if (connection instanceof OracleConnection) { OracleConnection oracleConn = (OracleConnection) connection; oracleConn.setClientInfo("MODULE", "YOUR_APP_NAME"); oracleConn.setClientInfo("ACTION", requestTraceId); } // 调用存储过程 currentStatement = connection.prepareCall("{call YOUR_STORED_PROCEDURE(?, ?, ?)}"); currentStatement.setObject(1, arg0); currentStatement.setObject(2, arg1); currentStatement.setObject(3, arg2); currentStatement.execute(); // 处理存储过程返回结果 ResultSet resultSet = currentStatement.getResultSet(); // ... 结果处理逻辑 return resultSet; } finally { // 确保资源关闭 if (currentStatement != null) { currentStatement.close(); } connection.close(); } } public void cancelCurrentProcedure() { if (currentStatement != null) { try { // 取消正在执行的存储过程 currentStatement.cancel(); } catch (SQLException e) { // 记录日志即可,无需抛出 log.warn("Failed to cancel stored procedure", e); } } } }
2. 配置数据库连接池,确保连接自动回收
使用Spring默认的HikariCP连接池,配置以下参数(在application.properties或application.yml中),避免连接长时间占用:
# HikariCP配置 spring.datasource.hikari.connection-timeout=30000 spring.datasource.hikari.idle-timeout=600000 spring.datasource.hikari.max-lifetime=1800000 spring.datasource.hikari.maximum-pool-size=10 spring.datasource.hikari.minimum-idle=2 spring.datasource.hikari.auto-commit=false
max-lifetime:连接最长生命周期,到期自动回收idle-timeout:空闲连接超时时间,避免闲置连接占用Oracle会话auto-commit=false:配合@Transactional手动管理事务,避免自动提交导致的会话残留
3. 验证Oracle会话是否已关闭
因为数据库是多用户共享环境,我们可以通过之前设置的MODULE和ACTION来精准查询自己的应用会话:
查询当前应用的活跃会话
SELECT username, module, action, sid, serial#, status FROM v$session WHERE username IS NOT NULL AND module = 'YOUR_APP_NAME' -- 对应Service中设置的MODULE ORDER BY status DESC;
- 当你取消请求后,对应的会话
status应该变为INACTIVE,并在连接池的idle-timeout到期后被回收 - 如果需要强制终止某个残留会话,可以用:
ALTER SYSTEM KILL SESSION '<sid>,<serial#>';
验证连接池状态
你也可以通过Spring Boot Actuator查看HikariCP的连接状态(需要添加Actuator依赖),确认活跃连接数在取消请求后是否减少:
# 开启Actuator连接池监控 management.endpoints.web.exposure.include=hikaricp
访问/actuator/hikaricp即可看到连接池的活跃、空闲连接数。
关键注意事项
- 事务回滚:因为你用了
@Transactional,当存储过程被取消时,抛出的SQLException会触发事务回滚,Oracle会自动回滚未提交的操作并释放会话 - 存储过程本身:如果存储过程中有长时间运行的循环或锁,即使调用
cancel()也可能需要一段时间才能终止,这时候需要确保存储过程本身支持中断(比如在循环中检查DBMS_APPLICATION_INFO.READ_CLIENT_INFO判断是否需要终止)
内容的提问来源于stack exchange,提问作者gabriel119435

