question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Spring Boot JPA; JpaRepository return Stream<T>; logAbandoned: true shows that Connection is abandoned.

See original GitHub issue

I did a small proof of concept with Spring Boot JPA, that has JpaRepository which has one custom method, that returns a Stream of entities. I turned on a bit of logging and currently it seems that though the Stream reading works well, then the database connection is abandoned. That concerns me a bit. Should I be worried and is this maybe a leakage?

application.yml:

spring:
  datasource:
    removeAbandonedTimeout: 30
    remove-abandoned: true
    log-abandoned: true

Code:

package my.test;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.stream.Stream;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@SpringBootApplication
@EnableScheduling
public class StreamErrorAppRunner {

    public static void main(String[] args) {
        SpringApplication.run(StreamErrorAppRunner.class);
    }

    @Bean
    public CommandLineRunner demo(TestEntityRepository testEntityRepository) {
        return (args) -> {
            testEntityRepository.save(new TestEntity("test_one"));
            testEntityRepository.save(new TestEntity("test_two"));
            testEntityRepository.save(new TestEntity("test_three"));
            testEntityRepository.save(new TestEntity("test_four"));
            testEntityRepository.save(new TestEntity("test_five"));
            testEntityRepository.save(new TestEntity("test_six"));
            testEntityRepository.save(new TestEntity("test_seven"));
        };
    }

}

@Component
class TestEntityLogger {
    private static final Logger logger = LoggerFactory.getLogger(TestEntityLogger.class);

    private final TestEntityRepository testEntityRepository;

    @Autowired
    TestEntityLogger(TestEntityRepository testEntityRepository) {
        this.testEntityRepository = testEntityRepository;
    }

    @Scheduled(fixedDelay = 60000, initialDelay = 5000)
    public void logAllTestEntities() {
        try (Stream<TestEntity> entities = testEntityRepository.findEntities()) {
            entities.forEach(e -> logger.info(e.toString()));
        }
        logger.info("Finished reading the stream. It should be closed by now.");
    }
}

@Entity
class TestEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private String name;

    public TestEntity() {
    }

    public TestEntity(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "TestEntity{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

interface TestEntityRepository extends JpaRepository<TestEntity, Long> {

    @Query("select t from TestEntity t")
    Stream<TestEntity> findEntities();

}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <artifactId>sb-jpa-stream-bug-poc</artifactId>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <groupId>my.test.app</groupId>
    <version>1.0-SNAPSHOT</version>

    <modelVersion>4.0.0</modelVersion>

    <dependencies>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.192</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>1.3.5.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

Relevant logs:

2016-06-20 10:46:18.115  INFO 10436 --- [           main] my.test.StreamErrorAppRunner             : Started StreamErrorAppRunner in 2.401 seconds (JVM running for 2.683)
2016-06-20 10:46:23.090  INFO 10436 --- [pool-2-thread-1] my.test.TestEntityLogger                 : TestEntity{id=1, name='test_one'}
2016-06-20 10:46:23.090  INFO 10436 --- [pool-2-thread-1] my.test.TestEntityLogger                 : TestEntity{id=2, name='test_two'}
2016-06-20 10:46:23.090  INFO 10436 --- [pool-2-thread-1] my.test.TestEntityLogger                 : TestEntity{id=3, name='test_three'}
2016-06-20 10:46:23.091  INFO 10436 --- [pool-2-thread-1] my.test.TestEntityLogger                 : TestEntity{id=4, name='test_four'}
2016-06-20 10:46:23.091  INFO 10436 --- [pool-2-thread-1] my.test.TestEntityLogger                 : TestEntity{id=5, name='test_five'}
2016-06-20 10:46:23.091  INFO 10436 --- [pool-2-thread-1] my.test.TestEntityLogger                 : TestEntity{id=6, name='test_six'}
2016-06-20 10:46:23.091  INFO 10436 --- [pool-2-thread-1] my.test.TestEntityLogger                 : TestEntity{id=7, name='test_seven'}
2016-06-20 10:46:23.092  INFO 10436 --- [pool-2-thread-1] my.test.TestEntityLogger                 : Finished reading the stream. It should be closed by now.
2016-06-20 10:47:01.998  WARN 10436 --- [:1466408776991]] o.a.tomcat.jdbc.pool.ConnectionPool      : Connection has been abandoned PooledConnection[conn9: url=jdbc:h2:mem:testdb user=SA]:java.lang.Exception
    at org.apache.tomcat.jdbc.pool.ConnectionPool.getThreadDump(ConnectionPool.java:1061)
    at org.apache.tomcat.jdbc.pool.ConnectionPool.borrowConnection(ConnectionPool.java:777)
    at org.apache.tomcat.jdbc.pool.ConnectionPool.borrowConnection(ConnectionPool.java:626)
    at org.apache.tomcat.jdbc.pool.ConnectionPool.getConnection(ConnectionPool.java:185)
    at org.apache.tomcat.jdbc.pool.DataSourceProxy.getConnection(DataSourceProxy.java:127)
    at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.getConnection(DatasourceConnectionProviderImpl.java:139)
    at org.hibernate.internal.AbstractSessionImpl$NonContextualJdbcConnectionAccess.obtainConnection(AbstractSessionImpl.java:380)
    at org.hibernate.engine.jdbc.internal.LogicalConnectionImpl.obtainConnection(LogicalConnectionImpl.java:228)
    at org.hibernate.engine.jdbc.internal.LogicalConnectionImpl.getConnection(LogicalConnectionImpl.java:171)
    at org.hibernate.engine.jdbc.internal.StatementPreparerImpl.connection(StatementPreparerImpl.java:63)
    at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$5.doPrepare(StatementPreparerImpl.java:162)
    at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$StatementPreparationTemplate.prepareStatement(StatementPreparerImpl.java:186)
    at org.hibernate.engine.jdbc.internal.StatementPreparerImpl.prepareQueryStatement(StatementPreparerImpl.java:160)
    at org.hibernate.loader.Loader.prepareQueryStatement(Loader.java:1885)
    at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1862)
    at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1839)
    at org.hibernate.loader.Loader.scroll(Loader.java:2627)
    at org.hibernate.loader.hql.QueryLoader.scroll(QueryLoader.java:561)
    at org.hibernate.hql.internal.ast.QueryTranslatorImpl.scroll(QueryTranslatorImpl.java:439)
    at org.hibernate.engine.query.spi.HQLQueryPlan.performScroll(HQLQueryPlan.java:355)
    at org.hibernate.internal.SessionImpl.scroll(SessionImpl.java:1381)
    at org.hibernate.internal.QueryImpl.scroll(QueryImpl.java:91)
    at org.springframework.data.jpa.provider.PersistenceProvider$HibernateScrollableResultsIterator.<init>(PersistenceProvider.java:414)
    at org.springframework.data.jpa.provider.PersistenceProvider$1.executeQueryWithResultStream(PersistenceProvider.java:118)
    at org.springframework.data.jpa.repository.query.JpaQueryExecution$StreamExecution.doExecute(JpaQueryExecution.java:323)
    at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:78)
    at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:100)
    at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:91)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:462)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:440)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:61)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:131)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208)
    at my.test.$Proxy55.findEntities(Unknown Source)
    at my.test.TestEntityLogger.logAllTestEntities(StreamErrorAppRunner.java:59)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:65)
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Reactions:1
  • Comments:6 (1 by maintainers)

github_iconTop GitHub Comments

2reactions
thomasdarimontcommented, Jun 20, 2016

@TarmoKalling since there is no JIRA issue yet I’m replying here.

if you add @Transactional(readOnly = true) to the logAllTestEntities() method then the connection should be released properly - according to a quick test.

cc @olivergierke FYI

Cheers, Thomas

0reactions
gokhanonercommented, Oct 28, 2016

@TarmoKalling if you can use Hibernate 5.2, here is a workaround for you 😉 (tested with spring-boot 1.4.1 and hibernate 5.2.3.Final )

@Component
class MyRepo {

    private final EntityManager entityManager;

    @Autowired
    private MyRepo(EntityManager entityManager) {
        this.entityManager = entityManager;
    }


    public Stream<TestEntity> findEntities() {
        return entityManager.unwrap(Session.class).createQuery("select t from TestEntity t", ToKas.class).setReadOnly(true).setFetchSize(1000).stream();
    }

}

@Transactional(readOnly = true) annotation needs to be on the method that process the Stream, on in your case on the logAllTestEntities Method, otherwise it closes the stream after method call.

I run this on H2 & Oracle DB with 500K records, works perfect.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Spring JpaRepository findAll, Java 8 Stream, Connection has ...
Just this nasty abandoned connection error that I do not like in my logs. This is just a minimal example. And if you...
Read more >
Spring Data JPA - Reference Documentation
Spring Data JPA provides repository support for the Jakarta Persistence API (JPA). It eases development of applications that need to access ...
Read more >
Connection Pooling and Data Sources - Developer
Advantages of this approach: Uses Tomcat JDBC connection pooling; Logging: Server will log and close abandoned connections; Helps identify application design ...
Read more >
Spring Data Java 8 Support - Baeldung
Spring Data now supports core Java 8 features – such as Optional, Stream API and CompletableFuture. In this quick article, we'll go through ......
Read more >
Spring Boot: Detecting Connection Leak - YouTube
In this video we will see how to detect connection leak in a Spring Boot application using HikariCP, the default database connection pool ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found