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

StreamingResponseBody异步场景如何确保OpenEntityManagerInViewFilter关闭EntityManager

异步请求(如StreamingResponseBody)中正确关闭EntityManager的方案

问题根源

同步请求中OpenEntityManagerInViewFilter会在请求结束时统一关闭EntityManager,但StreamingResponseBody这类异步场景下,默认的AsyncRequestInterceptor仅在超时或发生错误时才关闭会话,导致连接长期被占用,引发HikariCP连接泄漏。你之前尝试手动关闭注入的EntityManager无效,是因为@PersistenceContext注入的是请求级实例,其生命周期由Spring容器管控,手动close无法绕过容器的上下文绑定。

可行解决方案

1. 手动创建独立EntityManager并管理生命周期

避免复用请求级的EntityManager,直接从EntityManagerFactory创建独立实例,用完后显式关闭:

@RestController
public class MyController {
  
  @Autowired
  private EntityManagerFactory entityManagerFactory;

  @GetMapping(path = "/stream", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
  public StreamingResponseBody stream() {
    return outputStream -> {
      EntityManager em = null;
      try {
        em = entityManagerFactory.createEntityManager();
        // 配置批量查询减少内存占用
        TypedQuery<MyEntity> query = em.createQuery("SELECT e FROM MyEntity e", MyEntity.class);
        query.setHint(QueryHints.HINT_FETCH_SIZE, 50);
        
        // 流式处理查询结果
        Stream<MyEntity> resultStream = query.getResultStream();
        resultStream.forEach(entity -> {
          // 写入输出流的业务逻辑
          // 例如:outputStream.write(entity.toString().getBytes());
        });
      } finally {
        if (em != null && em.isOpen()) {
          em.close();
        }
      }
    };
  }
}

2. 自定义AsyncRequestInterceptor确保无条件关闭

替换默认的拦截器,让其在异步请求完成(无论成功/失败)时都关闭EntityManager:

第一步:实现自定义拦截器

public class CustomAsyncRequestInterceptor extends AsyncRequestInterceptor {
  private static final Logger logger = LoggerFactory.getLogger(CustomAsyncRequestInterceptor.class);

  @Override
  protected void closeSession() {
    if (this.sessionHolder != null && this.sessionHolder.getSession() != null) {
      logger.debug("Closing Hibernate Session after async request completion");
      SessionFactoryUtils.closeSession(this.sessionHolder.getSession());
    }
  }

  @Override
  public void afterCompletion(NativeWebRequest request, Exception ex) throws Exception {
    // 无论是否异常,强制关闭会话
    closeSession();
    super.afterCompletion(request, ex);
  }
}

第二步:配置过滤器使用自定义拦截器

@Configuration
public class WebConfig {
  @Bean
  public FilterRegistrationBean<OpenEntityManagerInViewFilter> openEntityManagerInViewFilter() {
    FilterRegistrationBean<OpenEntityManagerInViewFilter> registrationBean = new FilterRegistrationBean<>();
    OpenEntityManagerInViewFilter filter = new OpenEntityManagerInViewFilter();
    filter.setAsyncRequestInterceptor(new CustomAsyncRequestInterceptor());
    registrationBean.setFilter(filter);
    // 设置过滤器优先级,确保在其他过滤器之前执行
    registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return registrationBean;
  }
}

3. 使用独立事务管理EntityManager生命周期

通过@Transactional(propagation = Propagation.REQUIRES_NEW)创建独立事务,Spring会在事务结束后自动关闭对应的EntityManager:

@RestController
public class MyController {
  
  @Autowired
  private DataStreamingService streamingService;

  @GetMapping(path = "/stream", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
  public StreamingResponseBody stream() {
    return outputStream -> streamingService.streamDataToOutput(outputStream);
  }
}

@Service
public class DataStreamingService {
  @PersistenceContext
  private EntityManager entityManager;

  @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
  public void streamDataToOutput(OutputStream outputStream) {
    TypedQuery<MyEntity> query = entityManager.createQuery("SELECT e FROM MyEntity e", MyEntity.class);
    query.setHint(QueryHints.HINT_FETCH_SIZE, 50);
    
    Stream<MyEntity> resultStream = query.getResultStream();
    resultStream.forEach(entity -> {
      // 写入输出流逻辑
    });
  }
}

总结

优先推荐方案1或方案3:方案1完全掌控EntityManager生命周期,避免容器上下文干扰;方案3利用Spring事务机制自动管理,代码更简洁。方案2适合需要保留OpenEntityManagerInViewFilter原有逻辑的场景,但需注意拦截器的注册优先级。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 18:35:28