Reputation: 12182
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
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
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