Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Could not open JPA EntityManager for transaction; nested exception is java.lang.IllegalStateException

I am quite new to Spring and Spring-Batch in particular. Still I somehow managed to install the Spring Batch-Admin. I added custom jobs and Hibernate/JPA for persistence.

Everything is working as expected, up to the point where the first chunk should be persisted. Then I receive the following error-message:

org.springframework.transaction.CannotCreateTransactionException: 
      Could not open JPA  EntityManager for transaction;

nested exception is java.lang.IllegalStateException: Already value
      [org.springframework.jdbc.datasource.ConnectionHolder@60d31437] 
      for key [org.springframework.jdbc.datasource.DriverManagerDataSource@12da4b19] 
      bound to thread [jobLauncherTaskExecutor-1]

This is the full stacktrace:

org.springframework.transaction.CannotCreateTransactionException: Could not open JPA  EntityManager for transaction; nested exception is java.lang.IllegalStateException: Already value [org.springframework.jdbc.datasource.ConnectionHolder@43f9e588] for key [org.springframework.jdbc.datasource.DriverManagerDataSource@84f171a] bound to thread [jobLauncherTaskExecutor-1]
     at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:427)
     at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:371)
     at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:335)
     at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:105)
     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
     at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
     at com.sun.proxy.$Proxy41.saveIfUnique(Unknown Source)
     at com.qompa.batch.ArticleItemWriter.write(ArticleItemWriter.java:28)
     at org.springframework.batch.core.step.item.SimpleChunkProcessor.writeItems(SimpleChunkProcessor.java:171)
     at org.springframework.batch.core.step.item.SimpleChunkProcessor.doWrite(SimpleChunkProcessor.java:150)
     at org.springframework.batch.core.step.item.FaultTolerantChunkProcessor$3.doWithRetry(FaultTolerantChunkProcessor.java:313)
     at org.springframework.batch.retry.support.RetryTemplate.doExecute(RetryTemplate.java:240)
     at org.springframework.batch.retry.support.RetryTemplate.execute(RetryTemplate.java:187)
     at org.springframework.batch.core.step.item.BatchRetryTemplate.execute(BatchRetryTemplate.java:213)
     at org.springframework.batch.core.step.item.FaultTolerantChunkProcessor.write(FaultTolerantChunkProcessor.java:402)
     at org.springframework.batch.core.step.item.SimpleChunkProcessor.process(SimpleChunkProcessor.java:194)
     at org.springframework.batch.core.step.item.ChunkOrientedTasklet.execute(ChunkOrientedTasklet.java:74)
     at org.springframework.batch.core.step.tasklet.TaskletStep$ChunkTransactionCallback.doInTransaction(TaskletStep.java:386)
     at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:130)
     at org.springframework.batch.core.step.tasklet.TaskletStep$2.doInChunkContext(TaskletStep.java:264)
     at org.springframework.batch.core.scope.context.StepContextRepeatCallback.doInIteration(StepContextRepeatCallback.java:76)
     at org.springframework.batch.repeat.support.RepeatTemplate.getNextResult(RepeatTemplate.java:367)
     at org.springframework.batch.repeat.support.RepeatTemplate.executeInternal(RepeatTemplate.java:214)
     at org.springframework.batch.repeat.support.RepeatTemplate.iterate(RepeatTemplate.java:143)
     at org.springframework.batch.core.step.tasklet.TaskletStep.doExecute(TaskletStep.java:250)
     at org.springframework.batch.core.step.AbstractStep.execute(AbstractStep.java:195)
     at org.springframework.batch.core.job.SimpleStepHandler.handleStep(SimpleStepHandler.java:135)
     at org.springframework.batch.core.job.flow.JobFlowExecutor.executeStep(JobFlowExecutor.java:61)
     at org.springframework.batch.core.job.flow.support.state.StepState.handle(StepState.java:60)
     at org.springframework.batch.core.job.flow.support.SimpleFlow.resume(SimpleFlow.java:144)
     at org.springframework.batch.core.job.flow.support.SimpleFlow.start(SimpleFlow.java:124)
     at org.springframework.batch.core.job.flow.FlowJob.doExecute(FlowJob.java:135)
     at org.springframework.batch.core.job.AbstractJob.execute(AbstractJob.java:281)
     at org.springframework.batch.core.launch.support.SimpleJobLauncher$1.run(SimpleJobLauncher.java:120)
     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
     at java.lang.Thread.run(Thread.java:724)
Caused by: java.lang.IllegalStateException: Already value [org.springframework.jdbc.datasource.ConnectionHolder@43f9e588] for key [org.springframework.jdbc.datasource.DriverManagerDataSource@84f171a] bound to thread [jobLauncherTaskExecutor-1]
     at org.springframework.transaction.support.TransactionSynchronizationManager.bindResource(TransactionSynchronizationManager.java:189)
     at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:402)
... 36 more

The same Job executes fine in a standalone application. The problem occurs only in the Spring-Batch-Admin environment. Below you can see the project structure and dependencies:

enter image description here

This is the app-context.xml that overrides/extends the Batch-Admin configuration:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:batch="http://www.springframework.org/schema/batch"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:task="http://www.springframework.org/schema/task" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.1.xsd
        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.2.xsd">

    <context:component-scan base-package="com.company.batch" />

    <context:property-placeholder location="classpath:batch.properties" />

    <import resource="classpath:/META-INF/spring/batch/jobs/article-job.xml" />

    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${batch.jdbc.driver}" />
        <property name="url" value="${batch.jdbc.url}" />
        <property name="username" value="${batch.jdbc.user}" />
        <property name="password" value="${batch.jdbc.password}" />
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <bean id="entityManagerFactory"
        class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="packagesToScan" value="com.qompa.batch" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="database" value="POSTGRESQL"></property>
                <property name="showSql" value="true" />
                <property name="generateDdl" value="false" />
                <property name="databasePlatform" value="com.company.utils.persistence.CustomPGDialect" />
            </bean>
        </property>
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.hbm2ddl.auto"></prop>
            </props>
        </property>
    </bean>

    <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>
    <tx:annotation-driven transaction-manager="txManager" />

    <!-- schedule tasks -->
    <task:scheduled-tasks>
        <task:scheduled ref="articleRetrieval" method="run"
            cron="0 0 */4 * * *" />
        <task:scheduled ref="articleConversion" method="run"
            cron="0 15 */4 * * *" />
    </task:scheduled-tasks>
</beans>

What I understand so far is that it has to do with the ThreadPoolTaskExecutor to which the jobLauncherTaskExecutor bean refers. It seems to handle connection pooling for concurrently running jobs ... but to be honest I have no clue how to change my configurations to make these things work.

[Edit]: I am not even sure wether it is the afromentioned ThreadPoolTaskExecutor. But it seem s to be an implementation of the TaskExecutor interface.

If anyone ran into a similar issue, or has a suggestion how to configure my application in a way that transactions can be created for my persistence methods: Please give me a hint!

like image 801
achingfingers Avatar asked Jan 03 '14 15:01

achingfingers


4 Answers

The error comes from JpaTransactionManager line 403:

TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);

The error means that the transaction manager is trying to bind the datasource (not the entity manager) to the thread, but the datasource is already there and this is unexpected.

Note that the transaction manager had not started yet to bind the Entity Manager to the thread, that would happen next at JpaTransactionManager line 416:

There are two possible explanations:

  • Somebody (another transaction manager?) is adding the datasource to the thread before the transaction manager and this is unexpected.

  • Or no one is adding the datasource to the transaction manager, is just that at the end of the task execution no one cleans the thread before returning it to the pool, maybe due an error or an unhandled exception.

One question, does this also happen for only one execution thread, or only when there are several?

To find out what the problem is, these are some steps:

  • run with a minimal number of threads that cause the problem

  • put a breakpoint in TransactionSynchronizationManager.bindResource() to see who adds the connection to the thread. The breakpoint can be a conditional breakpoint with a condition on the thread name: "jobLauncherTaskExecutor-1".equals(Thread.currentThread().getName())

  • put also a breakpoint in TransactionSynchronizationManager.unbindResource(), to see if the datasource is unbound from the thread. when the breakpoints hit, scroll down the stacktrace and see which classes are causing this.

like image 185
Angular University Avatar answered Sep 17 '22 11:09

Angular University


This normally happens when you have multiple transaction managers in place.

Some hints.. When using annotaion @EnableBatchProcessing, Spring Batch automatically registers a transaction manager , and your JpaTransactionManager may never get used. If you want to change the transaction manager that spring batch uses, you have to implement the interface BatchConfigurer.(https://blog.codecentric.de/en/2013/06/spring-batch-2-2-javaconfig-part-3-profiles-and-environments/).

You can specify transaction manager for tasklets as follows:

<tasklet transaction-manager="transactionManager">
like image 20
Sinto Avatar answered Sep 18 '22 11:09

Sinto


If you have 2 dataSource, I suggest you to read:

https://github.com/spring-projects/spring-boot/issues/3012

So... configure the main datasource (is important the transaction manager's name)

@Configuration
@EnableJpaRepositories(
        entityManagerFactoryRef = "entityManager",
        transactionManagerRef = "transactionManager",
        basePackages = "a.b.c")
@PropertySource({"classpath:db_persistence.properties"})
@EnableTransactionManagement

and the other datasource:

@Configuration
@EnableJpaRepositories(
        entityManagerFactoryRef = "another_EntityManager",
        transactionManagerRef = "another_transactionManager",
        basePackages = "x.y.z")
@PropertySource({"classpath:db_persistence.properties"})
@EnableTransactionManagement

I hope that this help you.

like image 25
G.Dileo Avatar answered Sep 17 '22 11:09

G.Dileo


return stepBuilderFactory.get("orderStep1").<sourceBean, destBean>chunk(5)
            .reader(reader)
            .processor(batchFileRowProcessor)
            .writer(batchFileRowDataWritter)
            .taskExecutor(taskExecutor)
            .transactionManager(platformTransactionManager)
            .throttleLimit(1).build();

platformTransactionManager is the qualified bean from the data source configuration

like image 34
Ullas V Avatar answered Sep 18 '22 11:09

Ullas V