Search code examples
spring-bootjpaspring-dataspring-batchdatasource

Spring Boot Jpa Batch - CannotCreateTransactionException


I have a Spring Boot + JPA + Spring batch (+Spring Integration) project. Also there's Flyway, configured apart. I have configured spring batch to use a different datasource than default "spring.jpa.datasource". This is fine. Inside my tasklet I query on two datasources, first query on ds1 goes right, second query on ds2 goes wrong:

org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is java.lang.IllegalStateException: Already value [org.springframework.jdbc.datasource.ConnectionHolder@2fb082ff] for key [HikariDataSource (HikariPool-1)] bound to thread [main]

More details: I have a configuration class for each datasource, annotated with

@Configuration 
@EnableTransactionManagement
@EnableJpaRepositories

In each configuration a bean for DataSource, LocalContainerEntityManagerFactoryBean and JpaTransactionManager is defined, with custom name.

A service is defined for each datasource as:

 @Service @Transactional(transactionManager=JpaApsConfiguration.APS_TRANSACTION_MANAGER, propagation = Propagation.REQUIRES_NEW)

and a entitymanager is autowired using the right qualifier (note: query made through JpaRepository classes)

Any suggestion on what could cause this behaviour? Thanks

Note:

  • Database connects successfully
  • I had a different error before (already fixed, but maybe could be helpful): 'NoUniqueBeanDefinitionException' as Spring couldn't identify the correct 'PlatformTransactionManager' between my two transaction manager beans and the default transactionManager.

EDIT:

Here the complete stacktrace for the error:

2018-05-08 12:12:24 INFO o.s.batch.core.job.SimpleStepHandler - Executing step: [STEP#PROCESS_SHIPMENTS] 2018-05-08 12:12:24 ERROR o.s.batch.core.step.AbstractStep - Encountered an error executing step STEP#PROCESS_SHIPMENTS in job PROCESS_APS org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is java.lang.IllegalStateException: Already value [org.springframework.jdbc.datasource.ConnectionHolder@351e89fc] for key [HikariDataSource (HikariPool-1)] bound to thread [main] at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:450) at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:378) at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:474) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:289) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689) at dkr.astreconnector.service.GespeService$$EnhancerBySpringCGLIB$$73dbb368.getValueDetailByUniq() at dkr.astreconnector.batch.worker.ShipmentProcessor.process(ShipmentProcessor.java:63) at dkr.astreconnector.batch.worker.ShipmentProcessor.process(ShipmentProcessor.java:30) at org.springframework.batch.core.step.item.SimpleChunkProcessor.doProcess(SimpleChunkProcessor.java:126) at org.springframework.batch.core.step.item.SimpleChunkProcessor.transform(SimpleChunkProcessor.java:303) at org.springframework.batch.core.step.item.SimpleChunkProcessor.process(SimpleChunkProcessor.java:202) at org.springframework.batch.core.step.item.ChunkOrientedTasklet.execute(ChunkOrientedTasklet.java:75) at org.springframework.batch.core.step.tasklet.TaskletStep$ChunkTransactionCallback.doInTransaction(TaskletStep.java:406) at org.springframework.batch.core.step.tasklet.TaskletStep$ChunkTransactionCallback.doInTransaction(TaskletStep.java:330) at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:140) at org.springframework.batch.core.step.tasklet.TaskletStep$2.doInChunkContext(TaskletStep.java:272) at org.springframework.batch.core.scope.context.StepContextRepeatCallback.doInIteration(StepContextRepeatCallback.java:81) at org.springframework.batch.repeat.support.RepeatTemplate.getNextResult(RepeatTemplate.java:375) at org.springframework.batch.repeat.support.RepeatTemplate.executeInternal(RepeatTemplate.java:215) at org.springframework.batch.repeat.support.RepeatTemplate.iterate(RepeatTemplate.java:145) at org.springframework.batch.core.step.tasklet.TaskletStep.doExecute(TaskletStep.java:257) at org.springframework.batch.core.step.AbstractStep.execute(AbstractStep.java:200) at org.springframework.batch.core.job.SimpleStepHandler.handleStep(SimpleStepHandler.java:148) at org.springframework.batch.core.job.flow.JobFlowExecutor.executeStep(JobFlowExecutor.java:66) at org.springframework.batch.core.job.flow.support.state.StepState.handle(StepState.java:67) at org.springframework.batch.core.job.flow.support.SimpleFlow.resume(SimpleFlow.java:169) at org.springframework.batch.core.job.flow.support.SimpleFlow.start(SimpleFlow.java:144) at org.springframework.batch.core.job.flow.FlowJob.doExecute(FlowJob.java:136) at org.springframework.batch.core.job.AbstractJob.execute(AbstractJob.java:308) at org.springframework.batch.core.launch.support.SimpleJobLauncher$1.run(SimpleJobLauncher.java:141) at org.springframework.core.task.SyncTaskExecutor.execute(SyncTaskExecutor.java:50) at org.springframework.batch.core.launch.support.SimpleJobLauncher.run(SimpleJobLauncher.java:134) at org.springframework.boot.autoconfigure.batch.JobLauncherCommandLineRunner.execute(JobLauncherCommandLineRunner.java:163) at org.springframework.boot.autoconfigure.batch.JobLauncherCommandLineRunner.executeLocalJobs(JobLauncherCommandLineRunner.java:179) at org.springframework.boot.autoconfigure.batch.JobLauncherCommandLineRunner.launchJobFromProperties(JobLauncherCommandLineRunner.java:134) at org.springframework.boot.autoconfigure.batch.JobLauncherCommandLineRunner.run(JobLauncherCommandLineRunner.java:128) at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:790) at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:774) at org.springframework.boot.SpringApplication.run(SpringApplication.java:335) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1246) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1234) at dkr.astreconnector.AstreConnectorBatchApplication.main(AstreConnectorBatchApplication.java:14) Caused by: java.lang.IllegalStateException: Already value [org.springframework.jdbc.datasource.ConnectionHolder@351e89fc] for key [HikariDataSource (HikariPool-1)] bound to thread [main] at org.springframework.transaction.support.TransactionSynchronizationManager.bindResource(TransactionSynchronizationManager.java:193) at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:425) ... 43 common frames omitted 2018-05-08 12:12:24 INFO o.s.b.c.l.support.SimpleJobLauncher - Job: [FlowJob: [name=PROCESS_APS]] completed with the following parameters: [{run.id=128}] and the following status: [FAILED]


Solution

  • A little bit late, but I'd like to reply to @mad_fox

    Primary db:

    @Configuration
    @EnableJpaRepositories(
        entityManagerFactoryRef = JpaAppsConfiguration.APPS_ENTITY_MANAGER,
        transactionManagerRef = "transactionManager",
        basePackages = { JpaAppsConfiguration.APPS_REPOSITORIES })
    @EnableAutoConfiguration
    public class JpaAppsConfiguration {
    
    public final static String APPS_PERSISTENCE_UNIT = "apps";
    public final static String APPS_DATA_SOURCE = "appDs";
    public final static String APPS_ENTITY_MANAGER = "appsEmf";
    public final static String APPS_TRANSACTION_MANAGER = "appsTm";
    protected final static String APPS_PACKAGES = ""; //my models package
    protected final static String APPS_REPOSITORIES = ""; //my repos package
    
    @Primary
    @Bean(name = APPS_DATA_SOURCE)
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource appsDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Primary
    @Bean(name = APPS_ENTITY_MANAGER)
    @PersistenceContext(unitName = APPS_PERSISTENCE_UNIT)
    public LocalContainerEntityManagerFactoryBean appsEntityManagerFactory(EntityManagerFactoryBuilder builder,
            @Qualifier(APPS_DATA_SOURCE) DataSource appsDataSource) {
        LocalContainerEntityManagerFactoryBean emf = builder
                .dataSource(appsDataSource)
                .persistenceUnit(APPS_PERSISTENCE_UNIT)
                .packages(APPS_PACKAGES)
                .build();
    
        // Vendor adapter
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        emf.setJpaVendorAdapter(vendorAdapter);
        return emf;
    }
    }
    

    Secondary db:

    @Configuration
    @EnableJpaRepositories(
        entityManagerFactoryRef = JpaGepeConfiguration.GEPE_ENTITY_MANAGER, 
        basePackages = { JpaGepeConfiguration.GEPE_REPOSITORIES })
    @EnableAutoConfiguration
    public class JpaGepeConfiguration {
    
    public static final String GEPE_PERSISTENCE_UNIT = "gn1";
    public static final String GEPE_DATA_SOURCE = "gn1ds";
    public static final String GEPE_ENTITY_MANAGER = "gn1emf";
    public static final String GEPE_TRANSACTION_MANAGER = "gn1tm";
    public static final String GEPE_PACKAGES = ""; //my models package
    public static final String GEPE_REPOSITORIES = ""; //my repos package
    
    @Bean(name = GEPE_DATA_SOURCE)
    @ConfigurationProperties(prefix = "gn1.datasource")
    public DataSource gepeDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean(name = GEPE_ENTITY_MANAGER)
    @PersistenceContext(unitName = GEPE_PERSISTENCE_UNIT)
    public LocalContainerEntityManagerFactoryBean gepeEntityManagerFactory(EntityManagerFactoryBuilder builder,
            @Qualifier(GEPE_DATA_SOURCE) DataSource gepeDataSource) {
        LocalContainerEntityManagerFactoryBean emf = builder
                .dataSource(gepeDataSource)
                .persistenceUnit(GEPE_PERSISTENCE_UNIT)
                .packages(GEPE_PACKAGES)
                .build();
    
        // Vendor adapter
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        emf.setJpaVendorAdapter(vendorAdapter);
        return emf;
    }
    }
    

    Methods in @Service classes for primary db are annotated as:

    @Transactional(transactionManager = JpaAppsConfiguration.APPS_TRANSACTION_MANAGER, readOnly = false, propagation = Propagation.REQUIRED, noRollbackFor = Exception.class)
    

    Parameters in properties file are as follows:

    #DATABASE CONNECTION
    #Primary
    spring.datasource.username=
    spring.datasource.password=
    spring.datasource.driverClassName=
    spring.datasource.url=j
    spring.datasource.jdbcUrl=
    spring.datasource.name=
    #Secondary
    gepe.datasource.username=
    gepe.datasource.password=
    gepe.datasource.driverClassName=
    gepe.datasource.url=
    gepe.datasource.jdbcUrl=
    gepe.datasource.name=
    

    Hope this helps.