9

I have an issue with a spring batch ItemWriter that relies on a JPA repository in order to update data.

Here it is:

@Component
public class MessagesDigestMailerItemWriter implements ItemWriter<UserAccount> {

    private static final Logger log = LoggerFactory.getLogger(MessagesDigestMailerItemWriter.class);

    @Autowired
    private MessageRepository messageRepository;

    @Autowired
    private MailerService mailerService;

    @Override
    public void write(List<? extends UserAccount> userAccounts) throws Exception {
        log.info("Mailing messages digests and updating messages notification statuses");

        for (UserAccount userAccount : userAccounts) {
            if (userAccount.isEmailNotification()) {
                mailerService.mailMessagesDigest(userAccount);
            }
            for (Message message : userAccount.getReceivedMessages()) {
                message.setNotificationSent(true);
                messageRepository.save(message);//NOT SAVING!!
            }
        }
    }
}

Here is my Step configuration:

@Configuration
public class MailStepConfiguration {

    @Autowired
    private StepBuilderFactory stepBuilderFactory;

    @Autowired
    private EntityManagerFactory entityManagerFactory;

    @Autowired
    private MessagesDigestMailerItemWriter itemWriter;

    @Bean
    public Step messagesDigestMailingStep() {
        return stepBuilderFactory.get("messagesDigestMailingStep")//
                .<UserAccount, UserAccount> chunk(1)//
                .reader(jpaPagingItemReader(entityManagerFactory))//
                .writer(itemWriter)//
                .build();
    }

    @Bean(destroyMethod = "")
    @StepScope
    public static ItemReader<UserAccount> jpaPagingItemReader(EntityManagerFactory entityManagerFactory) {
        final JpaPagingItemReader<UserAccount> reader = new JpaPagingItemReader<>();
        reader.setEntityManagerFactory(entityManagerFactory);
        reader.setQueryString("SELECT ua FROM UserAccount ua JOIN FETCH ua.receivedMessages msg WHERE msg.notificationSent = false AND msg.messageRead = false");
        return reader;
    }

}

For completeness sake, here is my spring boot configuration:

@Configuration
@EnableBatchProcessing
@EnableAutoConfiguration
@ComponentScan("com.bignibou.batch.configuration")
public class Batch {
    public static void main(String[] args) {
        System.exit(SpringApplication.exit(new SpringApplicationBuilder(Batch.class).web(false).run(args)));
    }
}

and my datasource config:

@Configuration
@EnableJpaRepositories({ "com.bignibou.repository" })
@EntityScan("com.bignibou.domain")
public class DatasourceConfiguration {

    @Bean
    @ConfigurationProperties("spring.datasource.batch")
    public DataSource batchDatasource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    @ConfigurationProperties("spring.datasource.application")
    public DataSource applicationDatasource() {
        return DataSourceBuilder.create().build();
    }
}

I noticed that the execution flow get into the ItemWriter's write method and the messageRepository.save(message); does get executed but the data is not updated.

I suspect it is a transaction issue but I am not sure how to solve this problem...

edit: I forgot to mention that I have two Postgres databases:

  1. one for the job repository data
  2. another one for the application data.

I can confirm that data is written into the job repository database. The issue is with the application data. I am required to use distributed transactions bearing in mind the fact that I have two PG databases?

luboskrnac
  • 23,973
  • 10
  • 81
  • 92
balteo
  • 23,602
  • 63
  • 219
  • 412
  • It seems the only solution is to use a distributed tx manager. I have opened another question here: http://stackoverflow.com/questions/38323207 – balteo Jul 12 '16 at 13:55
  • hi @balteo, Have you got the solution? I'm facing the exact issue. Would be great if you can share the solution. – Krunal Shah Sep 08 '17 at 12:45
  • Hello @balteo, have you found a solution to this issue? I am trying to use Repository in writer in a similar way as you do, with similarly unsatisfying results... :-( – Petr Dvořák Sep 30 '17 at 20:34

2 Answers2

7

I opened an issue for this here:

https://jira.spring.io/browse/BATCH-2642

In principle, what helped us was to configure primary transaction manager like so:

@Configuration
public class JpaConfig {

    private final DataSource dataSource;

    @Autowired
    public JpaConfig(@Qualifier("dataSource") DataSource dataSource) {
        this.dataSource = dataSource;
    }

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

}

And then using autowired instance of transaction manager when configuring the step, like so:

@Autowired
private PlatformTransactionManager transactionManager;

private TaskletStep buildTaskletStep() {
        return stepBuilderFactory.get("SendCampaignStep")
                    .<UserAccount, UserAccount>chunk(pushServiceConfiguration.getCampaignBatchSize())
                    .reader(userAccountItemReader)
                    .processor(userAccountItemProcessor)
                    .writer(userAccountItemWriter)
                    .transactionManager(transactionManager)
                    .build();
    }
}

Data is now correctly persisted, but there is still some magic I don't fully get...

Petr Dvořák
  • 750
  • 1
  • 9
  • 21
  • Actually I am facing this issue and I am confused with this TransactionManager thing and Spring Data JPA. If I just set `dataSource`, will it be fine? Won't it affect to Spring Data JPA default configurations? And do I need to set some other things like `EntityManagerFactory`? – Dave Oct 19 '21 at 20:21
  • Thaaaaaaaaaaaaaaaaaaaaaank you so so much !!! you saved my life – Amine KOTNI Jan 11 '22 at 21:27
1

You should @EnableTransactionManagement in your main class. I believe Spring Boot will create transaction manager for you, but if you want to override defaults, you may want to configure it explicitly.

Spring Batch than provides APIs for changing transaction attributes.

luboskrnac
  • 23,973
  • 10
  • 81
  • 92
  • 2
    Thanks a lot for your answer. I tried `@EnableTransactionManagement` to no avail. I believed it was meant for use with `@Transaction` which use is not advised with Spring Batch. – balteo Jul 10 '16 at 08:59