James
James

Reputation: 12182

Spring Data Jpa repository in spring batch tasklet throws TransactionRequiredException

I'm having problems using a Spring Data Jpa repository inside a Spring Batch tasklet. I'd expect to have a valid hibernate transaction in the MyOrderTasklet's execute method using the myTransactionManager that is configured for this step. But as soon as flush() is called or if the execute method is left (without explicitly calling flush()). I get a

TransactionRequiredException"no transaction is in progress".

When debugging I see that a transaction gets created by spring batch before entering the tasklet's execute method and that a valid hibernateTransaction is created and put in the org.hibernate.jdbc.JDBCContext instance by calling getJpaDialect().beginTransaction() in org.springframework.orm.jpa.JpaTransactionManager#doBegin().

When methods of the orderRepository are called, I see that the call to AbstractPlatformTransactionManager#getTransaction finds an existing transaction and calls handleExistingTransaction. But later on org.hibernate.ejb.AbstractEntityManagerImpl#isTransactionInProgress returns false because no hibernateSession can be found.

I see that different Hibernate sessions and EntityManagers are created for transaction around the tasklet's execute method and for the calls to the repository. The outer hibernate transaction which is bound to the outer hibernate session can't be found by the inner hibernate session.

Any ideas how to solve this? How can the same hibernate session be used for the tasklet's execute method and the calls to the repository? Can the hibernateTemplate maybe be propagated to other sessions somehow?

Here's some code extract to show my setup:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackageClasses = MyRepositoryConfig.class)
@ComponentScan(basePackageClasses=MyRepositoryConfig.class)
public class MyRepositoryConfig {

    @Autowired
    private InfrastructureConfiguration infrastructureConfiguration;

    @Bean
    public EntityManagerFactory entityManagerFactory() throws SQLException {
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(infrastructureConfiguration.hibernateJpaVendorAdapter());
        factory.setPackagesToScan("com.example.model");
        factory.setDataSource(infrastructureConfiguration.dataSource());
        if (StringUtils.hasText(infrastructureConfiguration.getSchema())) {
            factory.getJpaPropertyMap().put("hibernate.default_schema", infrastructureConfiguration.getSchema());
        }
        factory.afterPropertiesSet();
        return factory.getObject();
    }

    @Bean(name= { "transactionManager", "myTransactionManager"})
    public PlatformTransactionManager transactionManager() throws SQLException {
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory());
        return txManager;
    }

    @Bean
    public HibernateExceptionTranslator hibernateExceptionTranslator() {
        return new HibernateExceptionTranslator();
    }

    @Bean
    public MyRepositoryService myRepositoryService() {
        return new myRepositoryServiceImpl();
    }
}

@Configuration
public class DefaultInfrastructureConfiguration implements InfrastructureConfiguration {

    @Value("${my.schema:MYSCHEMA}")
    private String defaultSchema;

    @Value("${novis.jdbc.url:jdbc:oracle:thin:@//example.com/example}")
    private String jdbcUrl;

    @Value("${novis.jdbc.username:scott}")
    private String jdbcUsername;

    @Value("${novis.jdbc.password:tiger}")
    private String jdbcPassword;

    @Value("${novis.jdbc.driverClassName:oracle.jdbc.driver.OracleDriver}")
    private String jdbcDriverClassName;

    @Value("${novis.hibernate.database:ORACLE}")
    private String hibernateDatabase;

    @Bean
    @Override
    public DataSource dataSource() {
        BasicDataSource ds = new BasicDataSource();

        ds.setDriverClassName(jdbcDriverClassName);
        ds.setUrl(jdbcUrl);
        ds.setUsername(jdbcUsername);
        ds.setPassword(jdbcPassword);
        ds.setTestWhileIdle(true);
        ds.setValidationQuery("SELECT 1 FROM DUAL");

        return ds;
    }

    @Override
    public HibernateJpaVendorAdapter hibernateJpaVendorAdapter() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(false);
        vendorAdapter.setDatabase(Database.valueOf(Database.class, hibernateDatabase));
        return vendorAdapter;
    }

    @Override
    public String getSchema() {
        return defaultSchema;
    }
}

@Configuration
@Import(MyRepositoryConfig.class)
public class OrderManagerConfig {
    @Autowired
    @Qualifier("myTransactionManager")
    private PlatformTransactionManager myTransactionManager;

    ....        
    @Bean
    public Tasklet MyOrderTasklet() {
        return new MyOrderTasklet();
    }
    ....
    @Bean
    public Step processMyOrderErrorsStep() {
        return steps.get("processMyOrderErrorsStep").
            transactionManager(myTransactionManager).
            tasklet(myOrderProcessor()).
            listener(stepExecutionLoggerListener()).
            build();
    }
    ....
}

public class MyOrderTasklet implements Tasklet {

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private PlatformTransactionManager myTransactionManager;

    @Override
    public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {

        for (Order order : orderRepository.findByErrorIsNotNullAndErrorHandledFalse()) {
            // Handle the error...
            order.setErrorHandled(true);
            orderRepository.saveAndFlush(order);
        }

        return RepeatStatus.FINISHED;
    }
}

Exception:

03 Sep 2014 09:53:39:902 ERROR AbstractStep:225 - Encountered an error executing step processMyOrderErrorsStep in job processMyOrderErrors
javax.persistence.TransactionRequiredException: no transaction is in progress
    at org.hibernate.ejb.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:959) ~[hibernate-entitymanager-3.6.10.Final.jar:3.6.10.Final]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.7.0_51]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) ~[?:1.7.0_51]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.7.0_51]
    at java.lang.reflect.Method.invoke(Method.java:606) ~[?:1.7.0_51]
    at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:342) ~[spring-orm-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at com.sun.proxy.$Proxy153.flush(Unknown Source) ~[?:?]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.7.0_51]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) ~[?:1.7.0_51]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.7.0_51]
    at java.lang.reflect.Method.invoke(Method.java:606) ~[?:1.7.0_51]
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:289) ~[spring-orm-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at com.sun.proxy.$Proxy153.flush(Unknown Source) ~[?:?]
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.flush(SimpleJpaRepository.java:436) ~[spring-data-jpa-1.6.1.RELEASE.jar:?]
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAndFlush(SimpleJpaRepository.java:404) ~[spring-data-jpa-1.6.1.RELEASE.jar:?]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.7.0_51]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) ~[?:1.7.0_51]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.7.0_51]
    at java.lang.reflect.Method.invoke(Method.java:606) ~[?:1.7.0_51]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:405) ~[spring-data-commons-1.8.1.RELEASE.jar:?]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:390) ~[spring-data-commons-1.8.1.RELEASE.jar:?]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:344) ~[spring-data-commons-1.8.1.RELEASE.jar:?]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:98) ~[spring-tx-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:262) ~[spring-tx-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95) ~[spring-tx-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136) ~[spring-tx-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodIntercceptor.invoke(CrudMethodMetadataPostProcessor.java:111) ~[spring-data-jpa-1.6.1.RELEASE.jar:?]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) ~[spring-aop-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207) ~[spring-aop-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at com.sun.proxy.$Proxy198.saveAndFlush(Unknown Source) ~[?:?]
    at ch.local.ordermanager.MyOrderTasklet.execute(MyOrderTasklet.java:41) ~[classes/:?]
    at org.springframework.batch.core.step.tasklet.TaskletStep$ChunkTransactionCallback.doInTransaction(TaskletStep.java:406) ~[spring-batch-core-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at org.springframework.batch.core.step.tasklet.TaskletStep$ChunkTransactionCallback.doInTransaction(TaskletStep.java:330) ~[spring-batch-core-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:133) ~[spring-tx-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.batch.core.step.tasklet.TaskletStep$2.doInChunkContext(TaskletStep.java:271) ~[spring-batch-core-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at org.springframework.batch.core.scope.context.StepContextRepeatCallback.doInIteration(StepContextRepeatCallback.java:77) ~[spring-batch-core-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at org.springframework.batch.repeat.support.RepeatTemplate.getNextResult(RepeatTemplate.java:368) ~[spring-batch-infrastructure-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at org.springframework.batch.repeat.support.RepeatTemplate.executeInternal(RepeatTemplate.java:215) ~[spring-batch-infrastructure-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at org.springframework.batch.repeat.support.RepeatTemplate.iterate(RepeatTemplate.java:144) ~[spring-batch-infrastructure-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at org.springframework.batch.core.step.tasklet.TaskletStep.doExecute(TaskletStep.java:257) ~[spring-batch-core-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at org.springframework.batch.core.step.AbstractStep.execute(AbstractStep.java:198) [spring-batch-core-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at org.springframework.batch.core.job.SimpleStepHandler.handleStep(SimpleStepHandler.java:148) [spring-batch-core-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at org.springframework.batch.core.job.AbstractJob.handleStep(AbstractJob.java:386) [spring-batch-core-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at org.springframework.batch.core.job.SimpleJob.doExecute(SimpleJob.java:135) [spring-batch-core-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at org.springframework.batch.core.job.AbstractJob.execute(AbstractJob.java:304) [spring-batch-core-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at org.springframework.batch.core.launch.support.SimpleJobLauncher$1.run(SimpleJobLauncher.java:135) [spring-batch-core-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at org.springframework.core.task.SyncTaskExecutor.execute(SyncTaskExecutor.java:50) [spring-core-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.batch.core.launch.support.SimpleJobLauncher.run(SimpleJobLauncher.java:128) [spring-batch-core-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at org.springframework.batch.core.launch.support.SimpleJobOperator.start(SimpleJobOperator.java:314) [spring-batch-core-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at ch.local.batchmanager.BatchJobRunner.runJob(BatchJobRunner.java:20) [classes/:?]
    at ch.local.batchmanager.BatchManager.run(BatchManager.java:30) [classes/:?]
    at ch.local.common.base.AbstractBatchApplication.execute(AbstractBatchApplication.java:38) [classes/:?]
    at ch.local.batchmanager.BatchManager.main(BatchManager.java:9) [classes/:?]

Upvotes: 4

Views: 5139

Answers (2)

James
James

Reputation: 12182

Although I defined the hibernate verison in the pom.xml to be 4.3.5, another library in our project has a dependency to hibernate 3.6.10, which was then used (and I did not notice). The error disappeared after getting rid of Hibernate 3 and using the latest hibernate 4 version (4.3.5 at the moment).

Upvotes: 0

Vlad Mihalcea
Vlad Mihalcea

Reputation: 153700

I suspect the following piece of code:

@Bean
public EntityManager entityManager(EntityManagerFactory entityManagerFactory) {
    return entityManagerFactory.createEntityManager();
}

Usually you don't configure an entityManager singleton, you let Spring decide when to create a new instance at transaction boundary.

The entity manager is usually being inserted using:

@PersistenceContext
EntityManager entityManager;

Even if you don't declare such bean, Spring will still create one and will provide you the one that's suitable for your current running transaction.

Using a singleton EntityManager is problematic, since the EntityManager is not thread-safe and reusing it without clearing might also lead to memory leaks and stale entity versions.

Upvotes: 3

Related Questions