Skip to content

Spring boot JPA repository passed to another thread not working

I have an autowired jpa repository object working. However, I need to use it to add rows into the database from multiple threads.
Though, after passing it to another thread, it fails. Code structure

@SpringBootApplication(exclude = HealthcheckConfig.class)
public class Application implements CommandLineRunner {

    @Autowired
    private DBRepository dbRepository;

    @Autowired
    private AppConfig appConfig;

    private ExecutorService executors = Executors.newFixedThreadPool(3);

    Application() {
    }

    @Override
    public void run(final String... args) {

        final DBSchemaObject temp = new Application("testdb", "testfield");
        dbRepository.save(temp); // This WORKs!!!
       
        for (FileStatus fileStatus: fileStatuses) {
            executors.execute(new ThreadSafeClass(dbRepository));
        }
    }

    public static void main(final String[] args) {
        new SpringApplicationBuilder(Application.class)
                .web(WebApplicationType.NONE)
                .run(args)
                .close();
    }
}

However, doing a dbRepository.save() from a thread safe class, I get error cause: java.lang.IllegalStateException: org.spring[email protected]41330d4f has been closed already

detailedMessage: Error creating bean with name 'spring.datasource-org.springframework.boot.autoconfigure.jdbc.DataSourceProperties': Could not bind properties to 'DataSourceProperties' : prefix=spring.datasource, ignoreInvalidFields=false, ignoreUnknownFields=true

Stacktrace:

{[email protected]} "org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800)"
{[email protected]} "org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:229)"
{[email protected]} "org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1354)"
{[email protected]} "org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1204)"
{[email protected]} "org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:564)"
{[email protected]} "org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)"
{[email protected]} "org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)"
{[email protected]} "org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)"
{[email protected]} "org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)"
{[email protected]} "org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)"
{[email protected]} "org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:410)"
{[email protected]} "org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1334)"
{[email protected]} "org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177)"
{[email protected]} "org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:564)"
{[email protected]} "org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)"
{[email protected]} "org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)"
{[email protected]} "org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)"
{[email protected]} "org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)"
{[email protected]} "org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)"
{[email protected]} "org.springframework.beans.factory.support.DefaultListableBeanFactory$1.orderedStream(DefaultListableBeanFactory.java:481)"
{[email protected]} "org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.detectPersistenceExceptionTranslators(PersistenceExceptionTranslationInterceptor.java:167)"
{[email protected]} "org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:149)"
{[email protected]} "org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)"
{[email protected]} "org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:174)"
{[email protected]} "org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)"
{[email protected]} "org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)"
{[email protected]} "org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)"
{[email protected]} "org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)"
{[email protected]} "com.sun.proxy.$Proxy99.save(Unknown Source)"
{[email protected]} "com.xxxx.run(Application.java:109)"
{[email protected]} "java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)"
{[email protected]} "java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)"
{[email protected]} "java.lang.Thread.run(Thread.java:748)"

How can I use the spring boot repository object across multiple threads ?

Answer

The problem is that your run() method just schedules the tasks to be executed, but does not wait for their completion. This what is happening:

  1. new SpringApplicationBuilder(Application.class) You are creating a new application context with the command line runner Application
  2. .run(args) Then you initialize and execute your application context’s run() method
  3. The run() method schedules the tasks to be executed and exists immediately:
 public void run(final String... args) {
        for (FileStatus fileStatus: fileStatuses) {
            executors.execute(new ThreadSafeClass(dbRepository));
        }
    }
  1. Because run() terminated, spring assumes that the application has finished and calls .close(); Thus closing the application context and making it impossible to use any spring features such as repositories.

  2. The scheduled tasks get executed, but the context was already closed, thus they fail and throw the exception.

The solution is to wait for the tasks’ completion before exiting from the run method. As your example is too minimal, this is just an example. Alternatively you can use other methods to wait for the completion of the tasks such as CountDownLatch , etc, without having to shutdown the thread pool:

for (FileStatus fileStatus: fileStatuses) {
    executors.execute(new ThreadSafeClass(dbRepository));
}
executors.shutdown(); // prevents the executor from accepting any new tasks
executors.awaitTermination(); // wait for the tasks to finish

ExecutorService::shutdown javadoc

ExecutorService::awaitTermination javadoc