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

如何确保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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 10:07:24