首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在StreamingResponseBody中加载实体时缺少JDBC连接

在StreamingResponseBody中加载实体时缺少JDBC连接
EN

Stack Overflow用户
提问于 2020-09-14 15:55:35
回答 2查看 672关注 0票数 0

我目前正在开发一种流媒体服务,我想用它以更高效的方式在客户端上播放音频文件。为了做到这一点,我有一个端点,它返回一个StreamingRepsonseBody,并且只返回一个音频文件的请求字节。

然而,在播放音频文件之后,我遇到了一个问题,就是对于我向服务器提出的任何请求都要获得一个CannotGetJdbcConnectionException (请在下面找到完整的堆栈跟踪)。

似乎数据库连接从未关闭过。异常(通常)在10个请求后抛出(我认为这也是打开连接的限制)。

我可以通过不调用数据库来“解决”这个问题,但这意味着客户端必须将我通常从数据库中获取的信息发送给我。

工作终点

如果通过bucketNamebucketFilename获取@RequestParam,则不需要从数据库中获取TrackEntity,并且流端点可以正常工作:

代码语言:javascript
复制
@RequestMapping(
        value = "/tracks/{trackId}/play",
        method = RequestMethod.GET,
        produces = MediaType.APPLICATION_OCTET_STREAM_VALUE
)
public StreamingResponseBody playTrack(
        @PathVariable("trackId") Long trackId,
        @RequestHeader("Range") String range,
        @RequestParam("bucketName") String bucketName,
        @RequestParam("bucketFilename") String bucketFilename
) {
    String[] rangeValues = range.split("=")[1].split("-");
    int rangeStart = Integer.parseInt(rangeValues[0]);
    int rangeEnd = Integer.parseInt(rangeValues[1]);
    int rangeSize = rangeEnd - rangeStart;

    InputStreamResource inputStreamResource = this.trackService
            .readChunk(bucketName, bucketFilename, rangeStart);

    return outputStream -> {
        try (InputStream inputStream = inputStreamResource.getInputStream()) {
            StreamUtils.copyRange(inputStream, outputStream, 0, rangeSize);
        }
    };
}

破碎的终点

应该是这样的。使用trackService,我希望从数据库加载TrackEntity。然而,这正是异常的来源。

代码语言:javascript
复制
@RequestMapping(
        value = "/tracks/{trackId}/play",
        method = RequestMethod.GET,
        produces = MediaType.APPLICATION_OCTET_STREAM_VALUE
)
public StreamingResponseBody playTrack(
        @PathVariable("trackId") Long trackId,
        @RequestHeader("Range") String range
) {
    String[] rangeValues = range.split("=")[1].split("-");
    // Fetch TrackEntity from the database
    TrackEntity track = this.trackService.getTrack(trackId);
    String bucketName = track.getBucketName();
    String bucketFilename = track.getBucketFilename();

    int rangeStart = Integer.parseInt(rangeValues[0]);
    int rangeEnd = Integer.parseInt(rangeValues[1]);
    int rangeSize = rangeEnd - rangeStart;

    InputStreamResource inputStreamResource = this.trackService
            .readChunk(bucketName, bucketFilename, rangeStart);

    return outputStream -> {

        try (InputStream inputStream = inputStreamResource.getInputStream()) {
            StreamUtils.copyRange(inputStream, outputStream, 0, rangeSize);
        }
        System.out.println(String.format("Fetched %.4f MB", (rangeSize / Math.pow(1024.0, 2))));
    };
}

这是getTrack()的实现。这里没什么令人兴奋的。

代码语言:javascript
复制
public TrackEntity getTrack(Long trackId) {
    return this.trackRepository.findById(trackId)
            .orElseThrow(ResourceNotFoundException::new);
}

全堆栈跟踪

这是抛出的异常的完整堆栈跟踪。

代码语言:javascript
复制
org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms.
    at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:82) ~[spring-jdbc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:612) ~[spring-jdbc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:669) ~[spring-jdbc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:700) ~[spring-jdbc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:712) ~[spring-jdbc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at org.springframework.jdbc.core.JdbcTemplate.queryForObject(JdbcTemplate.java:790) ~[spring-jdbc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at org.springframework.security.oauth2.provider.token.store.JdbcTokenStore.readAccessToken(JdbcTokenStore.java:160) ~[spring-security-oauth2-2.3.2.RELEASE.jar:na]
    at org.springframework.security.oauth2.provider.token.DefaultTokenServices.loadAuthentication(DefaultTokenServices.java:229) ~[spring-security-oauth2-2.3.2.RELEASE.jar:na]
    at org.springframework.security.oauth2.provider.token.DefaultTokenServices$$FastClassBySpringCGLIB$$5a1f25c.invoke(<generated>) ~[spring-security-oauth2-2.3.2.RELEASE.jar:na]
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:685) ~[spring-aop-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at org.springframework.security.oauth2.provider.token.DefaultTokenServices$$EnhancerBySpringCGLIB$$76cd39c0.loadAuthentication(<generated>) ~[spring-security-oauth2-2.3.2.RELEASE.jar:na]
    at org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager.authenticate(OAuth2AuthenticationManager.java:83) ~[spring-security-oauth2-2.3.2.RELEASE.jar:na]
    at org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter.doFilter(OAuth2AuthenticationProcessingFilter.java:150) ~[spring-security-oauth2-2.3.2.RELEASE.jar:na]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:92) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:77) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.security.web.access.channel.ChannelProcessingFilter.doFilter(ChannelProcessingFilter.java:157) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
    at io.ear.spring.config.SimpleCORSFilter.doFilter(SimpleCORSFilter.java:36) ~[classes/:na]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:367) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:860) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1591) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630) ~[na:na]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
    at java.base/java.lang.Thread.run(Thread.java:832) ~[na:na]
Caused by: java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms.
    at com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java:697) ~[HikariCP-3.4.1.jar:na]
    at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:196) ~[HikariCP-3.4.1.jar:na]
    at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:161) ~[HikariCP-3.4.1.jar:na]
    at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:128) ~[HikariCP-3.4.1.jar:na]
    at org.springframework.jdbc.datasource.DataSourceUtils.fetchConnection(DataSourceUtils.java:158) ~[spring-jdbc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:116) ~[spring-jdbc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:79) ~[spring-jdbc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    ... 64 common frames omitted

堆栈跟踪到“明显的连接泄漏检测到”

多亏了M. Deinum,我首先通过在application.properties中设置以下内容找到了漏洞:

代码语言:javascript
复制
logging.level.org.springframework.security=DEBUG
spring.datasource.hikari.leak-detection-threshold=15000

这让我想到了一个例外:

代码语言:javascript
复制
java.lang.Exception: Apparent connection leak detected
    at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:128) ~[HikariCP-3.4.1.jar:na]
    at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.getConnection(DatasourceConnectionProviderImpl.java:122) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]
    at org.hibernate.internal.NonContextualJdbcConnectionAccess.obtainConnection(NonContextualJdbcConnectionAccess.java:38) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]
    at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.acquireConnectionIfNeeded(LogicalConnectionManagedImpl.java:104) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]
    at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.getPhysicalConnection(LogicalConnectionManagedImpl.java:134) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]
    at org.hibernate.internal.SessionImpl.connection(SessionImpl.java:462) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na]
    at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:282) ~[spring-core-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:266) ~[spring-core-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle.doGetConnection(HibernateJpaDialect.java:430) ~[spring-orm-5.2.2.RELEASE.jar:5.2.2.RELEASE]
   ...
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-09-15 17:52:02

  1. 首先不要使用会导致问题的SimpleTaskExecutor (它将为每个传入请求创建Thread对象,可能无法正确清除它们。

)

  1. 您的代码可能会出现资源泄漏,方法是不关闭InputStream和/或进入一个连续循环.

使用JPA的

  1. ,默认情况下启用了spring.jpa.open-in-view属性(如果没有显式设置,在启动时会收到警告)。您可能希望将其切换到false,以便只在需要时打开会话。

要修复1,只需删除bean定义,Spring将为您配置一个合适的TaskExecutor (开箱即用)。

对于数字2,请重写控制器中的代码以防止资源泄漏。

代码语言:javascript
复制
@GetMapping(
        value = "/tracks/{trackId}/play",
        produces = MediaType.APPLICATION_OCTET_STREAM_VALUE
)
public StreamingResponseBody playTrack(
        @PathVariable("trackId") Long trackId,
        @RequestHeader("Range") String range
) {

    String[] rangeValues = range.split("=")[1].split("-");

    int rangeStart = Integer.parseInt(rangeValues[0]);
    int rangeEnd = Integer.parseInt(rangeValues[1]);

    return outputStream -> {
        InputStreamResource inputStreamResource = this.trackService.playTrack(trackId);
        try (InputStream inputStream = inputStreamResource.getInputStream()) {
          StreamUtils.copyRange(inputStream, outputStream, rangeStart, rangeEnd);        
        }       
    };
}

这段代码将关闭InputStreamStreamUtils.copyRange将永远不会进入无限循环(并且它会清理代码)。

第3条很容易修复,将以下内容添加到application.properties中。

代码语言:javascript
复制
spring.jpa.open-in-view=false

然而,这可能会对应用程序的其他部分产生影响,使其无法正常工作。特别是如果您有一个视图,并且依赖于视图中的开放会话模式来自动检索尚未检索到的数据(即实体中的惰性关系)。

票数 3
EN

Stack Overflow用户

发布于 2020-09-14 16:36:11

@EnableAsync添加到配置类并添加@Async("yourTaskExecuter")

@EnableAsync注释切换了Spring在后台线程池中运行@Async方法的能力。

票数 -2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/63887817

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档