membersound
membersound

Reputation: 86637

JpaItemWriter: no transaction is in progress

I'd like to use JpaItemWriter to batch persist entities. But when I use the following code to persist, I'm told:

Hibernate: 
    select
        nextval ('hibernate_sequence')
[] 2014-03-19 15:46:02,237 ERROR : TransactionRequiredException: no transaction is in progress

How can I enable transactions on the following:

@Bean
public ItemWriter<T> writer() {
    JpaItemWriter<T> itemWriter = new JpaItemWriter<>();
    itemWriter.setEntityManagerFactory(emf);
}

@Configuration
@EnableTransactionManagement
@EnableBatchProcessing 
class Config{ {
     @Bean
    public LocalContainerEntityManagerFactoryBean emf() {
        LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
        emf.setDataSource(dataSource());
        emf.setPackagesToScan("my.package");
        emf.setJpaVendorAdapter(jpaAdapter());
        emf.setJpaProperties(jpaProterties());
        return emf;
}

Edit:

@Bean
public Job airlineJob(JobBuilderFactory jobs, Step step) {
    return jobs.get("job")
            .start(step)
            .build();
}

//Reader is a `FlatFileItemReader`, writer is `CustomItemWriter`.
@Bean
public Step step(StepBuilderFactory steps,
        MultiResourceItemReader<T> rea,
        ItemProcessor<T, T> pro,
        ItemWriter<T> wr) {
    return steps.get("step")
            .reader(rea)
            .processor(proc)
            .writer(wr)
            .build();
}

//use same datasource and tx manager as in the full web application
@Bean
public JobLauncher launcher(TransactionManager tx, DataSource ds) throws Exception {
    SimpleJobLauncher launcher = new SimpleJobLauncher();

    JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
    factory.setDataSource(ds);
    factory.setTransactionManager(tx);

    jobLauncher.setJobRepository(factory.getJobRepository());
    return launcher;
}

Edit 2 as response to @Haim:

@Bean
    public JpaItemWriter<T> jpaItemWriter(EntityManagerFactory emf) {
        JpaItemWriter<T> writer = new JpaItemWriter<T>();
        writer.setEntityManagerFactory(emf);
        return writer;
    }

Upvotes: 10

Views: 13118

Answers (3)

Bharathiraja
Bharathiraja

Reputation: 812

Write your own JpaTransactionManager for your datasources instead of spring transactions

   @Autowired
   private DataSource dataSource;

   @Bean
   @Primary
   public JpaTransactionManager jpaTransactionManager() {
        final JpaTransactionManager tm = new JpaTransactionManager();
        tm.setDataSource(dataSource);
        return tm;
   }

Click Up Vote if this is useful for you.

Upvotes: 2

Haim Raman
Haim Raman

Reputation: 12023

I agree with Michael Minella: Spring batch job repository does not like to share its transaction manager with others. The logic is simple, if you share your job transaction manager with your step transaction manager upon step failure it will rollback both the step and the data written to the job repository. This means that you will not persist data for job restart. In order to use two transaction managers you need to:

Delete @EnableTransactionManagement in case you use it only for the @Transactional above
Define an additional transaction manager

@Bean
@Qualifier("jpaTrx")
public PlatformTransactionManager jpaTransactionManager() {
       return new JpaTransactionManager(emf());
}

Set the transaction manager to your step

@Autowired
@Qualifier("jpaTrx")
PlatformTransactionManager jpaTransactionManager

 //Reader is a FlatFileItemReader, writer is CustomItemWriter.
    @Bean
    public Step step(StepBuilderFactory steps,
            MultiResourceItemReader<T> rea,
            ItemProcessor<T, T> pro,
            ItemWriter<T> wr) {
        return steps.get("step")
                //attach tx manager
                .transactionManager(jpaTransactionManager)
                .reader(rea)
                .processor(proc)
                .writer(wr)
                .build();
    }

Upvotes: 18

membersound
membersound

Reputation: 86637

I solved it creating my own transactional JpaWriter:

@Component
public class CustomItemWriter<T> extends JpaItemWriter<T> {
    @Override
    @Transactional
    public void write(List<? extends T> items) {
        super.write(items);
    }
}

Upvotes: 0

Related Questions