Spring批处理tasklet中的Spring Data Jpa存储库抛出TransactionRequiredException

时间:2014-09-03 08:24:32

标签: java spring hibernate jpa transactions

我在Spring Batch tasklet中使用Spring Data Jpa存储库时遇到问题。 我期望在MyOrderTasklet's execute方法中使用为此步骤配置的myTransactionManager进行有效的休眠交易。但是只要调用flush()或者保留execute方法(没有显式调用flush())。我得到了

TransactionRequiredException"no transaction is in progress".

调试时,我发现在进入tasklet的execute方法之前,Spring批处理创建了一个事务,并且创建了一个有效的hibernateTransaction,并通过调用org.hibernate.jdbc.JDBCContext将其放入getJpaDialect().beginTransaction()实例中org.springframework.orm.jpa.JpaTransactionManager#doBegin()

当调用orderRepository的方法时,我看到对AbstractPlatformTransactionManager#getTransaction的调用找到了现有的事务并调用了handleExistingTransaction。但是后来org.hibernate.ejb.AbstractEntityManagerImpl#isTransactionInProgress返回false,因为找不到hibernateSession。

我看到围绕tasklet的execute方法和对存储库的调用创建了不同的Hibernate会话和EntityManagers。内部hibernate会话无法找到绑定到外部hibernate会话的外部hibernate事务。

任何想法如何解决这个问题?如何将相同的hibernate会话用于tasklet的execute方法和对存储库的调用? hibernateTemplate可能会以某种方式传播到其他会话吗?

以下是一些代码摘录以显示我的设置:

@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;
    }
}

例外:

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/:?]

2 个答案:

答案 0 :(得分:3)

我怀疑以下代码:

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

通常你不配置entityManager单例,你让Spring决定何时在事务边界创建一个新实例。

通常使用以下方式插入实体管理器:

@PersistenceContext
EntityManager entityManager;

即使您没有声明这样的bean,Spring仍会创建一个,并为您提供适合您当前正在运行的事务的那个。

使用单例EntityManager是有问题的,因为EntityManager不是线程安全的,重用它而不清除也可能导致内存泄漏和陈旧的实体版本。

答案 1 :(得分:0)

虽然我将pom.xml中的hibernate verison定义为4.3.5,但我们项目中的另一个库依赖于hibernate 3.6.10,然后使用它(我没注意到)。在摆脱Hibernate 3并使用最新的hibernate 4版本(目前为4.3.5)后,错误消失了。