10

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;
    }
membersound
  • 81,582
  • 193
  • 585
  • 1,120
  • Why are you constructing a JpaItemWriter yuorself? That should be done by spring? If there is no transaction in progress it means you haven't setup Spring Batch correctly. Add the relevant configuration and classes. – M. Deinum Mar 19 '14 at 15:24
  • Because a batch process needs reader and writer defined as a Bean. `stepBuilderFactory.get("step").reader(reader).writer(writer).build();` – membersound Mar 19 '14 at 15:35
  • Ah you are using Spring Batch 3 with the Java Config approach. For starters you shouldn't call `afterPropertiesSet` spring will do that for you (it is just a bean like anyother). But please still add your actual Spring Batch and transaction configuration. I would expect you also have a `JpaTransactionManager` and some where a `@EnableTransactionManagement`. – M. Deinum Mar 19 '14 at 15:37
  • Yes exactly. I have transaction working fine already with JPA in my app. Just during the import when using `JpaItemWriter` somehow the transaction is not active. – membersound Mar 19 '14 at 15:43
  • Please add your Spring Batch configuration... There is too little information, you should wire the transaction manager to the `JobRepository` and optionally to the `Job`. – M. Deinum Mar 19 '14 at 15:46
  • A couple points: 1. When you use `@EnableBatchProcessing` (which I don't see here), you get a jobLauncher sop you don't need the configuration for the launcher. 2. Return the most specific type you can when using Java config. 3. Are you using the same datasource in all areas? 4. Are you using the same transaction manager between the JPA configuration and the batch configuration? I can't tell from the code above. – Michael Minella Mar 20 '14 at 22:26
  • OK sorry. Yes I have EnableBatchProcessing. I use the same datasource + also the same tx throughout all beans. – membersound Mar 21 '14 at 07:01

3 Answers3

18

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();
    }
Haim Raman
  • 11,508
  • 6
  • 44
  • 70
  • 1
    Thanks for your answer. Using a separate entitymanger works using your approach. But spring complains about `JpaItemWriter` not having `EntityManager` defined. So I created a `@Bean` where I create the `JpaItemWriter` using `new` operator and set the emf by injection (see my edited question above). It that correct? – membersound Mar 31 '14 at 10:15
  • 1
    That's fine. It depends on the place you add the transaction manager configuration. In my example I assumed that all are placed in the same class. I would prefer to place transaction manager in the same file as the emf and inject it at the second configuration file. see my update – Haim Raman Mar 31 '14 at 16:35
  • I added the default "PlatformTransactionManager transactionMananger" for my JobRepository and defined the JPA transaction manager like you suggested above for my Step but my .saveAll() method is still NOT writing to database.... any ideas?? it looks like something is going wrong in the SpringAOP class JdkDynamicAppProxy.class where it returns a "DefaultTransaction" object in the invoke() method, but I have another branch that works for my application (using older spring 1.x.x) and it returns the correct ArrayList of my entity type here – ennth Apr 14 '21 at 20:16
2

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.

Bharathiraja
  • 714
  • 1
  • 12
  • 20
0

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);
    }
}
membersound
  • 81,582
  • 193
  • 585
  • 1,120
  • This won't work correctly. `@Transactional` causes issues with Spring Batch because of the manual transaction management Spring Batch has internally. As @m-deinum requests, please post the configuration for your actual batch job if you're interested in addressing this correctly. – Michael Minella Mar 20 '14 at 13:18
  • Have you tried all error scenarios? Transaction rollbacks? Retry/skip? There are known issues with using @Transactional with Spring Batch and it is recommended not to use it. – Michael Minella Mar 20 '14 at 16:54
  • I'd be happy for any alternative suggestions, therefore I updated my answer with my job configuration stuff. – membersound Mar 20 '14 at 18:22